use super::GraphQLGenerator;
use crate::codegen::common::case_conversion::to_snake_case;
use crate::codegen::graphql::sdl::{SdlBuilder, TargetLanguage, TypeMapper};
use crate::codegen::graphql::spec_parser::{GraphQLField, GraphQLSchema, TypeKind};
use anyhow::Result;
#[derive(Default, Debug, Clone, Copy)]
pub struct RubyGenerator;
impl RubyGenerator {
fn generate_rbs_signatures(&self, schema: &GraphQLSchema) -> Result<String> {
let mut rbs = String::new();
rbs.push_str("# DO NOT EDIT - Auto-generated by Spikard CLI\n");
rbs.push_str("# RBS type signatures for GraphQL types\n");
rbs.push_str("# Generated from GraphQL schema for Steep type checking\n\n");
rbs.push_str("module Types\n");
let mapper = TypeMapper::new(TargetLanguage::Ruby, Some(schema));
for (type_name, type_def) in &schema.types {
if matches!(
type_name.as_str(),
"String" | "Int" | "Float" | "Boolean" | "ID" | "DateTime" | "Date" | "Time" | "JSON" | "Upload"
) {
continue;
}
match type_def.kind {
TypeKind::Object => {
rbs.push_str(&format!(" class {}\n", type_def.name));
rbs.push_str(" < GraphQL::Schema::Object\n\n");
for field in &type_def.fields {
if field.name.is_empty() {
continue;
}
let field_type = mapper.map_type_with_list_nullability(
&field.type_name,
field.is_nullable,
field.is_list,
field.list_item_nullable,
);
rbs.push_str(&format!(" def {}: () -> {}\n", field.name, field_type));
}
rbs.push_str(" end\n\n");
}
TypeKind::InputObject => {
rbs.push_str(&format!(" class {}\n", type_def.name));
rbs.push_str(" < GraphQL::Schema::InputObject\n\n");
for field in &type_def.input_fields {
if field.name.is_empty() {
continue;
}
let field_type = mapper.map_type_with_list_nullability(
&field.type_name,
field.is_nullable,
field.is_list,
field.list_item_nullable,
);
rbs.push_str(&format!(" def {}: () -> {}\n", field.name, field_type));
}
rbs.push_str(" end\n\n");
}
TypeKind::Enum => {
rbs.push_str(&format!(" class {}\n", type_def.name));
rbs.push_str(" < GraphQL::Schema::Enum\n\n");
for value in &type_def.enum_values {
rbs.push_str(&format!(" VALUE_{}: ::\n", value.name.to_uppercase()));
}
rbs.push_str(" end\n\n");
}
TypeKind::Scalar => {
rbs.push_str(&format!(" class {}\n", type_def.name));
rbs.push_str(" < GraphQL::Types::Relay::Node\n");
rbs.push_str(" end\n\n");
}
TypeKind::Union => {
rbs.push_str(&format!(" class {}\n", type_def.name));
rbs.push_str(" < GraphQL::Schema::Union\n\n");
if !type_def.possible_types.is_empty() {
rbs.push_str(" def self.resolve_type(obj: untyped, ctx: untyped): String\n");
}
rbs.push_str(" end\n\n");
}
TypeKind::Interface => {
rbs.push_str(&format!(" module {}\n", type_def.name));
rbs.push_str(" < GraphQL::Schema::Interface\n\n");
for field in &type_def.fields {
if field.name.is_empty() {
continue;
}
let field_type = mapper.map_type_with_list_nullability(
&field.type_name,
field.is_nullable,
field.is_list,
field.list_item_nullable,
);
rbs.push_str(&format!(" def {}: () -> {}\n", field.name, field_type));
}
rbs.push_str(" end\n\n");
}
_ => {}
}
}
rbs.push_str(" # Query resolver\n");
rbs.push_str(" class QueryType\n");
rbs.push_str(" < GraphQL::Schema::Object\n\n");
for field in &schema.queries {
let field_type = mapper.map_type_with_list_nullability(
&field.type_name,
field.is_nullable,
field.is_list,
field.list_item_nullable,
);
let method_name = to_snake_case(&field.name);
if field.arguments.is_empty() {
rbs.push_str(&format!(" def {method_name}: () -> {field_type}\n"));
} else {
let mut arg_types = Vec::new();
for arg in &field.arguments {
let arg_type = mapper.map_type_with_list_nullability(
&arg.type_name,
arg.is_nullable,
arg.is_list,
arg.list_item_nullable,
);
arg_types.push(format!("{}: {}", to_snake_case(&arg.name), arg_type));
}
rbs.push_str(&format!(
" def {}: ({}) -> {}\n",
method_name,
arg_types.join(", "),
field_type
));
}
}
rbs.push_str(" end\n\n");
if !schema.mutations.is_empty() {
rbs.push_str(" # Mutation resolver\n");
rbs.push_str(" class MutationType\n");
rbs.push_str(" < GraphQL::Schema::Object\n\n");
for field in &schema.mutations {
let field_type = mapper.map_type_with_list_nullability(
&field.type_name,
field.is_nullable,
field.is_list,
field.list_item_nullable,
);
let method_name = to_snake_case(&field.name);
if field.arguments.is_empty() {
rbs.push_str(&format!(" def {method_name}: () -> {field_type}\n"));
} else {
let mut arg_types = Vec::new();
for arg in &field.arguments {
let arg_type = mapper.map_type_with_list_nullability(
&arg.type_name,
arg.is_nullable,
arg.is_list,
arg.list_item_nullable,
);
arg_types.push(format!("{}: {}", to_snake_case(&arg.name), arg_type));
}
rbs.push_str(&format!(
" def {}: ({}) -> {}\n",
method_name,
arg_types.join(", "),
field_type
));
}
}
rbs.push_str(" end\n\n");
}
if !schema.subscriptions.is_empty() {
rbs.push_str(" # Subscription resolver\n");
rbs.push_str(" class SubscriptionType\n");
rbs.push_str(" < GraphQL::Schema::Object\n\n");
for field in &schema.subscriptions {
let field_type = mapper.map_type_with_list_nullability(
&field.type_name,
field.is_nullable,
field.is_list,
field.list_item_nullable,
);
let method_name = to_snake_case(&field.name);
if field.arguments.is_empty() {
rbs.push_str(&format!(" def {method_name}: () -> {field_type}\n"));
} else {
let mut arg_types = Vec::new();
for arg in &field.arguments {
let arg_type = mapper.map_type_with_list_nullability(
&arg.type_name,
arg.is_nullable,
arg.is_list,
arg.list_item_nullable,
);
arg_types.push(format!("{}: {}", to_snake_case(&arg.name), arg_type));
}
rbs.push_str(&format!(
" def {}: ({}) -> {}\n",
method_name,
arg_types.join(", "),
field_type
));
}
}
rbs.push_str(" end\n\n");
}
rbs.push_str(" # Schema definition\n");
rbs.push_str(" class AppSchema\n");
rbs.push_str(" < GraphQL::Schema\n\n");
rbs.push_str(" def self.query: Class\n");
if !schema.mutations.is_empty() {
rbs.push_str(" def self.mutation: Class\n");
}
if !schema.subscriptions.is_empty() {
rbs.push_str(" def self.subscription: Class\n");
}
rbs.push_str(" end\n");
rbs.push_str("end\n");
Ok(rbs)
}
fn file_header(&self) -> &'static str {
"# frozen_string_literal: true\n\n# DO NOT EDIT - Auto-generated by Spikard CLI\n#\n# This file was automatically generated from your GraphQL schema.\n# Any manual changes will be overwritten on the next generation.\n\n"
}
fn comment_line(&self, indent: &str, text: &str) -> String {
format!("{indent}# {}\n", text.replace('\n', " "))
}
fn escape_single_quoted(&self, text: &str) -> String {
text.replace('\\', "\\\\").replace('\'', "\\'")
}
fn ruby_graphql_type(&self, type_name: &str) -> String {
match type_name {
"String" => "String".to_string(),
"Int" => "Integer".to_string(),
"Float" => "Float".to_string(),
"Boolean" => "Boolean".to_string(),
"ID" => "ID".to_string(),
"DateTime" => "GraphQL::Types::ISO8601DateTime".to_string(),
"Date" => "GraphQL::Types::ISO8601Date".to_string(),
"Time" => "GraphQL::Types::ISO8601Time".to_string(),
"JSON" => "GraphQL::Types::JSON".to_string(),
other => format!("Types::{other}"),
}
}
fn ruby_graphql_type_expr(&self, type_name: &str, is_list: bool, list_item_nullable: bool) -> String {
let base_type = self.ruby_graphql_type(type_name.trim_matches(|c| c == '!' || c == '[' || c == ']'));
if is_list {
if list_item_nullable {
format!("[{base_type}, null: true]")
} else {
format!("[{base_type}]")
}
} else {
base_type
}
}
fn resolver_signature(&self, field: &GraphQLField) -> String {
if field.arguments.is_empty() {
format!(" def {}\n", to_snake_case(&field.name))
} else {
let args = field
.arguments
.iter()
.map(|arg| format!("{}:", to_snake_case(&arg.name)))
.collect::<Vec<_>>()
.join(", ");
format!(" def {}({args})\n", to_snake_case(&field.name))
}
}
fn resolver_stub(&self, owner: &str, field: &GraphQLField) -> String {
format!(
" raise NotImplementedError, 'TODO: Implement {}#{}'\n",
owner,
self.escape_single_quoted(&to_snake_case(&field.name))
)
}
fn trim_trailing_blank_lines(&self, code: &mut String) {
while code.ends_with("\n\n") {
code.pop();
}
}
fn strip_generated_header<'a>(&self, source: &'a str) -> &'a str {
source.strip_prefix(self.file_header()).unwrap_or(source).trim()
}
}
impl GraphQLGenerator for RubyGenerator {
fn generate_complete(&self, schema: &GraphQLSchema) -> Result<String> {
let mut code = String::new();
code.push_str(self.file_header());
code.push_str(self.strip_generated_header(&self.generate_types(schema)?));
code.push_str("\n\n");
code.push_str(self.strip_generated_header(&self.generate_resolvers(schema)?));
code.push_str("\n\n");
code.push_str(self.strip_generated_header(&self.generate_schema_definition(schema)?));
if !code.ends_with('\n') {
code.push('\n');
}
Ok(code)
}
fn generate_types(&self, schema: &GraphQLSchema) -> Result<String> {
let mut code = String::new();
code.push_str(self.file_header());
code.push_str("module Types\n");
for (type_name, type_def) in &schema.types {
if matches!(
type_name.as_str(),
"String" | "Int" | "Float" | "Boolean" | "ID" | "DateTime" | "Date" | "Time" | "JSON" | "Upload"
) {
continue;
}
match type_def.kind {
TypeKind::Object => {
code.push_str(
&self.comment_line(
" ",
type_def
.description
.as_deref()
.unwrap_or(&format!("GraphQL object type {}.", type_def.name)),
),
);
code.push_str(&format!(" class {} < GraphQL::Schema::Object\n", type_def.name));
for field in &type_def.fields {
if field.name.is_empty() {
continue; }
if let Some(field_desc) = &field.description {
code.push_str(&self.comment_line(" ", field_desc));
}
if field.arguments.is_empty() {
let field_type =
self.ruby_graphql_type_expr(&field.type_name, field.is_list, field.list_item_nullable);
let null_option = if field.is_nullable { "null: true" } else { "null: false" };
code.push_str(&format!(" field :{}, {}, {}\n", field.name, field_type, null_option));
if let Some(reason) = &field.deprecation_reason {
code.push_str(&format!(
" deprecation_reason '{}'\n",
self.escape_single_quoted(reason)
));
}
} else {
code.push_str(&format!(" field :{} do\n", field.name));
for arg in &field.arguments {
if arg.name.is_empty() {
continue; }
let arg_type =
self.ruby_graphql_type_expr(&arg.type_name, arg.is_list, arg.list_item_nullable);
let required = if arg.is_nullable {
"required: false"
} else {
"required: true"
};
code.push_str(&format!(" argument :{}, {}, {}\n", arg.name, arg_type, required));
}
code.push_str(" end\n");
}
}
code.push_str(" end\n\n");
}
TypeKind::InputObject => {
code.push_str(
&self.comment_line(
" ",
type_def
.description
.as_deref()
.unwrap_or(&format!("GraphQL input object {}.", type_def.name)),
),
);
code.push_str(&format!(" class {} < GraphQL::Schema::InputObject\n", type_def.name));
for field in &type_def.input_fields {
if field.name.is_empty() {
continue; }
if let Some(field_desc) = &field.description {
code.push_str(&self.comment_line(" ", field_desc));
}
let field_type =
self.ruby_graphql_type_expr(&field.type_name, field.is_list, field.list_item_nullable);
let required = if field.is_nullable {
"required: false"
} else {
"required: true"
};
code.push_str(&format!(" argument :{}, {}, {}\n", field.name, field_type, required));
}
code.push_str(" end\n\n");
}
TypeKind::Enum => {
code.push_str(
&self.comment_line(
" ",
type_def
.description
.as_deref()
.unwrap_or(&format!("GraphQL enum {}.", type_def.name)),
),
);
code.push_str(&format!(" class {} < GraphQL::Schema::Enum\n", type_def.name));
for value in &type_def.enum_values {
if let Some(desc) = &value.description {
code.push_str(&self.comment_line(" ", desc));
}
code.push_str(&format!(" value '{}'\n", self.escape_single_quoted(&value.name)));
if value.is_deprecated
&& let Some(reason) = &value.deprecation_reason
{
code.push_str(&format!(
" deprecation_reason '{}'\n",
self.escape_single_quoted(reason)
));
}
}
code.push_str(" end\n\n");
}
TypeKind::Scalar => {
code.push_str(
&self.comment_line(
" ",
type_def
.description
.as_deref()
.unwrap_or(&format!("Custom GraphQL scalar {}.", type_def.name)),
),
);
code.push_str(&format!(" class {} < GraphQL::Schema::Scalar\n", type_def.name));
code.push_str(&format!(
" graphql_name '{}'\n",
self.escape_single_quoted(&type_def.name)
));
code.push_str(" end\n\n");
}
TypeKind::Union => {
code.push_str(
&self.comment_line(
" ",
type_def
.description
.as_deref()
.unwrap_or(&format!("GraphQL union {}.", type_def.name)),
),
);
code.push_str(&format!(" class {} < GraphQL::Schema::Union\n", type_def.name));
for possible_type in &type_def.possible_types {
code.push_str(&format!(" possible_type Types::{possible_type}\n"));
}
code.push_str(" end\n\n");
}
TypeKind::Interface => {
code.push_str(
&self.comment_line(
" ",
type_def
.description
.as_deref()
.unwrap_or(&format!("GraphQL interface {}.", type_def.name)),
),
);
code.push_str(&format!(" module {}\n", type_def.name));
code.push_str(" include GraphQL::Schema::Interface\n");
for field in &type_def.fields {
if field.name.is_empty() {
continue; }
if let Some(field_desc) = &field.description {
code.push_str(&self.comment_line(" ", field_desc));
}
let field_type =
self.ruby_graphql_type_expr(&field.type_name, field.is_list, field.list_item_nullable);
let null_option = if field.is_nullable { "null: true" } else { "null: false" };
code.push_str(&format!(" field :{}, {}, {}\n", field.name, field_type, null_option));
}
code.push_str(" end\n\n");
}
_ => {}
}
}
self.trim_trailing_blank_lines(&mut code);
code.push_str("end\n");
Ok(code)
}
fn generate_resolvers(&self, schema: &GraphQLSchema) -> Result<String> {
let mut code = String::new();
code.push_str(self.file_header());
code.push_str("# Resolves GraphQL query fields.\n");
code.push_str("class QueryType < GraphQL::Schema::Object\n");
if schema.queries.is_empty() {
code.push_str(" # No queries defined\n");
} else {
for (index, field) in schema.queries.iter().enumerate() {
if index > 0 {
code.push('\n');
}
if let Some(desc) = &field.description {
code.push_str(&self.comment_line(" ", desc));
}
code.push_str(&self.resolver_signature(field));
code.push_str(&self.resolver_stub("QueryType", field));
code.push_str(" end\n");
}
}
code.push_str("end\n\n");
if !schema.mutations.is_empty() {
code.push_str("# Resolves GraphQL mutation fields.\n");
code.push_str("class MutationType < GraphQL::Schema::Object\n");
for (index, field) in schema.mutations.iter().enumerate() {
if index > 0 {
code.push('\n');
}
if let Some(desc) = &field.description {
code.push_str(&self.comment_line(" ", desc));
}
code.push_str(&self.resolver_signature(field));
code.push_str(&self.resolver_stub("MutationType", field));
code.push_str(" end\n");
}
code.push_str("end\n\n");
}
if !schema.subscriptions.is_empty() {
code.push_str("# Resolves GraphQL subscription fields.\n");
code.push_str("class SubscriptionType < GraphQL::Schema::Object\n");
for (index, field) in schema.subscriptions.iter().enumerate() {
if index > 0 {
code.push('\n');
}
if let Some(desc) = &field.description {
code.push_str(&self.comment_line(" ", desc));
}
code.push_str(&self.resolver_signature(field));
code.push_str(&self.resolver_stub("SubscriptionType", field));
code.push_str(" end\n");
}
code.push_str("end\n\n");
}
self.trim_trailing_blank_lines(&mut code);
Ok(code)
}
fn generate_schema_definition(&self, schema: &GraphQLSchema) -> Result<String> {
let mut code = String::new();
code.push_str(self.file_header());
let builder = SdlBuilder::new(schema);
let sdl = builder.build();
code.push_str("# Root GraphQL schema for the generated application.\n");
code.push_str("class AppSchema < GraphQL::Schema\n");
code.push_str(" query QueryType\n");
if !schema.mutations.is_empty() {
code.push_str(" mutation MutationType\n");
}
if !schema.subscriptions.is_empty() {
code.push_str(" subscription SubscriptionType\n");
}
code.push_str("end\n\n");
code.push_str("# GraphQL Schema Definition Language (SDL)\n");
code.push_str("SCHEMA_SDL = <<~SDL\n");
for line in sdl.lines() {
if line.is_empty() {
code.push('\n');
continue;
}
code.push_str(" ");
code.push_str(line.trim_end());
code.push('\n');
}
code.push_str("SDL\n");
Ok(code)
}
fn generate_type_signatures(&self, schema: &GraphQLSchema) -> Result<String> {
self.generate_rbs_signatures(schema)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codegen::graphql::spec_parser::GraphQLArgument;
use std::collections::HashMap;
#[test]
fn test_to_snake_case_via_shared_utility() {
assert_eq!(to_snake_case("getUser"), "get_user");
assert_eq!(to_snake_case("createUserProfile"), "create_user_profile");
assert_eq!(to_snake_case("user"), "user");
assert_eq!(to_snake_case("HTTPServer"), "http_server");
}
#[test]
fn test_type_mapper_ruby_scalars() {
let mapper = TypeMapper::new(TargetLanguage::Ruby, None);
assert_eq!(mapper.map_scalar("String"), "String");
assert_eq!(mapper.map_scalar("Int"), "Integer");
assert_eq!(mapper.map_scalar("Float"), "Float");
assert_eq!(mapper.map_scalar("Boolean"), "true | false");
assert_eq!(mapper.map_scalar("ID"), "String");
assert_eq!(mapper.map_scalar("CustomType"), "CustomType");
}
#[test]
fn test_type_mapper_ruby_nullability() {
let mapper = TypeMapper::new(TargetLanguage::Ruby, None);
assert_eq!(mapper.map_type("String", false, false), "String");
assert_eq!(mapper.map_type("String", true, false), "String | nil");
assert_eq!(mapper.map_type("Integer", false, false), "Integer");
assert_eq!(mapper.map_type("Integer", true, false), "Integer | nil");
}
#[test]
fn test_type_mapper_ruby_lists() {
let mapper = TypeMapper::new(TargetLanguage::Ruby, None);
assert_eq!(mapper.map_type("String", false, true), "Array[String | nil]");
assert_eq!(
mapper.map_type_with_list_nullability("String", false, true, false),
"Array[String]"
);
assert_eq!(mapper.map_type("String", true, true), "Array[String | nil] | nil");
assert_eq!(
mapper.map_type_with_list_nullability("String", true, true, false),
"Array[String] | nil"
);
}
#[test]
fn test_sdl_builder_generates_schema() {
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![],
mutations: vec![],
subscriptions: vec![],
directives: vec![],
description: None,
};
let builder = SdlBuilder::new(&schema);
let sdl = builder.build();
assert_eq!(sdl.trim(), "");
}
#[test]
fn test_generate_types_empty_schema() {
let generator = RubyGenerator;
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![],
mutations: vec![],
subscriptions: vec![],
directives: vec![],
description: None,
};
let result = generator.generate_types(&schema).unwrap();
assert!(result.contains("module Types"));
assert!(result.contains("end"));
assert!(result.contains("DO NOT EDIT - Auto-generated by Spikard CLI"));
}
#[test]
fn test_generate_resolvers_with_query() {
let generator = RubyGenerator;
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![GraphQLField {
name: "hello".to_string(),
type_name: "String".to_string(),
is_list: false,
list_item_nullable: false,
is_nullable: true,
arguments: vec![],
description: None,
deprecation_reason: None,
}],
mutations: vec![],
subscriptions: vec![],
directives: vec![],
description: None,
};
let result = generator.generate_resolvers(&schema).unwrap();
assert!(result.contains("class QueryType"));
assert!(result.contains("def hello"));
assert!(result.contains("NotImplementedError"));
assert!(result.contains("TODO"));
}
#[test]
fn test_generate_resolvers_with_arguments() {
let generator = RubyGenerator;
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![GraphQLField {
name: "user".to_string(),
type_name: "User".to_string(),
is_list: false,
list_item_nullable: false,
is_nullable: true,
arguments: vec![GraphQLArgument {
name: "id".to_string(),
type_name: "String".to_string(),
is_nullable: false,
is_list: false,
list_item_nullable: false,
default_value: None,
description: None,
}],
description: None,
deprecation_reason: None,
}],
mutations: vec![],
subscriptions: vec![],
directives: vec![],
description: None,
};
let result = generator.generate_resolvers(&schema).unwrap();
assert!(result.contains("def user(id:)"));
assert!(result.contains("NotImplementedError"));
}
#[test]
fn test_generate_schema_definition() {
let generator = RubyGenerator;
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![GraphQLField {
name: "hello".to_string(),
type_name: "String".to_string(),
is_list: false,
list_item_nullable: false,
is_nullable: true,
arguments: vec![],
description: None,
deprecation_reason: None,
}],
mutations: vec![],
subscriptions: vec![],
directives: vec![],
description: None,
};
let result = generator.generate_schema_definition(&schema).unwrap();
assert!(result.contains("class AppSchema < GraphQL::Schema"));
assert!(result.contains("query QueryType"));
assert!(result.contains("SCHEMA_SDL"));
}
#[test]
fn test_generate_rbs_signatures_empty_schema() {
let generator = RubyGenerator;
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![],
mutations: vec![],
subscriptions: vec![],
directives: vec![],
description: None,
};
let result = generator.generate_rbs_signatures(&schema).unwrap();
assert!(result.contains("# RBS type signatures for GraphQL types"));
assert!(result.contains("module Types"));
assert!(result.contains("class QueryType"));
assert!(result.contains("< GraphQL::Schema::Object"));
assert!(result.contains("class AppSchema"));
assert!(result.contains("< GraphQL::Schema"));
assert!(result.contains("def self.query: Class"));
}
#[test]
fn test_generate_rbs_signatures_with_query() {
let generator = RubyGenerator;
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![GraphQLField {
name: "hello".to_string(),
type_name: "String".to_string(),
is_list: false,
list_item_nullable: false,
is_nullable: true,
arguments: vec![],
description: None,
deprecation_reason: None,
}],
mutations: vec![],
subscriptions: vec![],
directives: vec![],
description: None,
};
let result = generator.generate_rbs_signatures(&schema).unwrap();
assert!(result.contains("def hello: () -> String | nil"));
}
#[test]
fn test_generate_rbs_signatures_with_arguments() {
let generator = RubyGenerator;
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![GraphQLField {
name: "user".to_string(),
type_name: "User".to_string(),
is_list: false,
list_item_nullable: false,
is_nullable: true,
arguments: vec![GraphQLArgument {
name: "id".to_string(),
type_name: "String".to_string(),
is_nullable: false,
is_list: false,
list_item_nullable: false,
default_value: None,
description: None,
}],
description: None,
deprecation_reason: None,
}],
mutations: vec![],
subscriptions: vec![],
directives: vec![],
description: None,
};
let result = generator.generate_rbs_signatures(&schema).unwrap();
assert!(result.contains("def user: (id: String) -> User | nil"));
}
#[test]
fn test_generate_rbs_signatures_with_mutation() {
let generator = RubyGenerator;
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![],
mutations: vec![GraphQLField {
name: "createUser".to_string(),
type_name: "User".to_string(),
is_list: false,
list_item_nullable: false,
is_nullable: true,
arguments: vec![GraphQLArgument {
name: "name".to_string(),
type_name: "String".to_string(),
is_nullable: false,
is_list: false,
list_item_nullable: false,
default_value: None,
description: None,
}],
description: None,
deprecation_reason: None,
}],
subscriptions: vec![],
directives: vec![],
description: None,
};
let result = generator.generate_rbs_signatures(&schema).unwrap();
assert!(result.contains("class MutationType"));
assert!(result.contains("def create_user: (name: String) -> User | nil"));
assert!(result.contains("def self.mutation: Class"));
}
#[test]
fn test_generate_rbs_signatures_with_subscription() {
let generator = RubyGenerator;
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![],
mutations: vec![],
subscriptions: vec![GraphQLField {
name: "userUpdated".to_string(),
type_name: "User".to_string(),
is_list: false,
list_item_nullable: false,
is_nullable: false,
arguments: vec![],
description: None,
deprecation_reason: None,
}],
directives: vec![],
description: None,
};
let result = generator.generate_rbs_signatures(&schema).unwrap();
assert!(result.contains("class SubscriptionType"));
assert!(result.contains("def user_updated: () -> User"));
assert!(result.contains("def self.subscription: Class"));
}
#[test]
fn test_generate_rbs_signatures_with_list_type() {
let generator = RubyGenerator;
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![GraphQLField {
name: "users".to_string(),
type_name: "User".to_string(),
is_list: true,
list_item_nullable: true,
is_nullable: false,
arguments: vec![],
description: None,
deprecation_reason: None,
}],
mutations: vec![],
subscriptions: vec![],
directives: vec![],
description: None,
};
let result = generator.generate_rbs_signatures(&schema).unwrap();
assert!(result.contains("def users: () -> Array[User | nil]"));
}
#[test]
fn test_generate_type_signatures_trait_method() {
let generator = RubyGenerator;
let schema = GraphQLSchema {
types: HashMap::new(),
queries: vec![GraphQLField {
name: "hello".to_string(),
type_name: "String".to_string(),
is_list: false,
list_item_nullable: false,
is_nullable: true,
arguments: vec![],
description: None,
deprecation_reason: None,
}],
mutations: vec![],
subscriptions: vec![],
directives: vec![],
description: None,
};
let result = generator.generate_type_signatures(&schema).unwrap();
assert!(result.contains("# RBS type signatures"));
assert!(result.contains("def hello: () -> String | nil"));
}
}