1use std::collections::HashMap;
7
8#[cfg(feature = "openapi")]
9use utoipa::ToSchema;
10
11#[derive(Debug, Clone)]
13#[cfg_attr(feature = "openapi", derive(ToSchema))]
14pub struct GraphQLType {
15 pub name: String,
17
18 pub kind: GraphQLTypeKind,
20
21 pub description: Option<String>,
23
24 pub fields: Vec<GraphQLField>,
26
27 pub enum_values: Vec<String>,
29
30 pub interfaces: Vec<String>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36#[cfg_attr(feature = "openapi", derive(ToSchema))]
37pub enum GraphQLTypeKind {
38 Object,
40 Enum,
42 Interface,
44 Input,
46}
47
48#[derive(Debug, Clone)]
50#[cfg_attr(feature = "openapi", derive(ToSchema))]
51pub struct GraphQLField {
52 pub name: String,
54
55 pub field_type: String,
57
58 pub required: bool,
60
61 pub is_list: bool,
63
64 pub description: Option<String>,
66
67 pub arguments: Vec<GraphQLArgument>,
69}
70
71#[derive(Debug, Clone)]
73#[cfg_attr(feature = "openapi", derive(ToSchema))]
74pub struct GraphQLArgument {
75 pub name: String,
77
78 pub arg_type: String,
80
81 pub required: bool,
83
84 pub default_value: Option<String>,
86}
87
88impl GraphQLType {
89 pub fn new(name: String, kind: GraphQLTypeKind) -> Self {
91 Self {
92 name,
93 kind,
94 description: None,
95 fields: Vec::new(),
96 enum_values: Vec::new(),
97 interfaces: Vec::new(),
98 }
99 }
100
101 pub fn with_description(mut self, description: String) -> Self {
103 self.description = Some(description);
104 self
105 }
106
107 pub fn add_field(&mut self, field: GraphQLField) {
109 self.fields.push(field);
110 }
111
112 pub fn add_enum_value(&mut self, value: String) {
114 self.enum_values.push(value);
115 }
116
117 pub fn add_interface(&mut self, interface: String) {
119 self.interfaces.push(interface);
120 }
121
122 pub fn to_sdl(&self) -> String {
124 let mut lines = Vec::new();
125
126 if let Some(desc) = &self.description {
128 lines.push(format!("\"\"\"{}\"\"\"", desc));
129 }
130
131 match self.kind {
133 GraphQLTypeKind::Object => {
134 let interfaces = if self.interfaces.is_empty() {
135 String::new()
136 } else {
137 format!(" implements {}", self.interfaces.join(" & "))
138 };
139 lines.push(format!("type {}{} {{", self.name, interfaces));
140 for field in &self.fields {
141 lines.push(format!(" {}", field.to_sdl()));
142 }
143 lines.push("}".to_string());
144 }
145 GraphQLTypeKind::Enum => {
146 lines.push(format!("enum {} {{", self.name));
147 for value in &self.enum_values {
148 lines.push(format!(" {}", value));
149 }
150 lines.push("}".to_string());
151 }
152 GraphQLTypeKind::Interface => {
153 lines.push(format!("interface {} {{", self.name));
154 for field in &self.fields {
155 lines.push(format!(" {}", field.to_sdl()));
156 }
157 lines.push("}".to_string());
158 }
159 GraphQLTypeKind::Input => {
160 lines.push(format!("input {} {{", self.name));
161 for field in &self.fields {
162 lines.push(format!(" {}", field.to_sdl()));
163 }
164 lines.push("}".to_string());
165 }
166 }
167
168 lines.join("\n")
169 }
170}
171
172impl GraphQLField {
173 pub fn new(name: String, field_type: String) -> Self {
175 Self {
176 name,
177 field_type,
178 required: false,
179 is_list: false,
180 description: None,
181 arguments: Vec::new(),
182 }
183 }
184
185 pub fn required(mut self) -> Self {
187 self.required = true;
188 self
189 }
190
191 pub fn list(mut self) -> Self {
193 self.is_list = true;
194 self
195 }
196
197 pub fn with_description(mut self, description: String) -> Self {
199 self.description = Some(description);
200 self
201 }
202
203 pub fn add_argument(&mut self, argument: GraphQLArgument) {
205 self.arguments.push(argument);
206 }
207
208 pub fn to_sdl(&self) -> String {
210 let mut result = String::new();
211
212 if let Some(desc) = &self.description {
214 result.push_str(&format!("\"\"\"{}\"\"\" ", desc));
215 }
216
217 result.push_str(&self.name);
218
219 if !self.arguments.is_empty() {
221 result.push('(');
222 let args: Vec<String> = self.arguments.iter().map(|a| a.to_sdl()).collect();
223 result.push_str(&args.join(", "));
224 result.push(')');
225 }
226
227 result.push_str(": ");
228
229 let type_str = if self.is_list {
231 format!("[{}]", self.field_type)
232 } else {
233 self.field_type.clone()
234 };
235
236 result.push_str(&type_str);
237
238 if self.required {
239 result.push('!');
240 }
241
242 result
243 }
244}
245
246impl GraphQLArgument {
247 pub fn new(name: String, arg_type: String) -> Self {
249 Self {
250 name,
251 arg_type,
252 required: false,
253 default_value: None,
254 }
255 }
256
257 pub fn required(mut self) -> Self {
259 self.required = true;
260 self
261 }
262
263 pub fn with_default(mut self, default: String) -> Self {
265 self.default_value = Some(default);
266 self
267 }
268
269 pub fn to_sdl(&self) -> String {
271 let mut result = format!("{}: {}", self.name, self.arg_type);
272
273 if self.required {
274 result.push('!');
275 }
276
277 if let Some(default) = &self.default_value {
278 result.push_str(&format!(" = {}", default));
279 }
280
281 result
282 }
283}
284
285pub struct GraphQLSchemaGenerator {
287 pub include_descriptions: bool,
289
290 types: HashMap<String, GraphQLType>,
292}
293
294impl GraphQLSchemaGenerator {
295 pub fn new() -> Self {
297 Self {
298 include_descriptions: true,
299 types: HashMap::new(),
300 }
301 }
302
303 pub fn generate_workflow_schema(&mut self) -> String {
305 self.generate_workflow_type();
307 self.generate_metadata_type();
308 self.generate_node_type();
309 self.generate_edge_type();
310 self.generate_node_kind_enum();
311 self.generate_execution_state_enum();
312 self.generate_query_type();
313 self.generate_mutation_type();
314
315 self.to_sdl()
317 }
318
319 fn generate_workflow_type(&mut self) {
321 let mut workflow_type = GraphQLType::new("Workflow".to_string(), GraphQLTypeKind::Object);
322
323 if self.include_descriptions {
324 workflow_type = workflow_type
325 .with_description("A workflow defining a sequence of operations".to_string());
326 }
327
328 workflow_type.add_field(GraphQLField::new("id".to_string(), "ID".to_string()).required());
329
330 workflow_type.add_field(
331 GraphQLField::new("metadata".to_string(), "WorkflowMetadata".to_string()).required(),
332 );
333
334 workflow_type.add_field(
335 GraphQLField::new("nodes".to_string(), "Node".to_string())
336 .list()
337 .required(),
338 );
339
340 workflow_type.add_field(
341 GraphQLField::new("edges".to_string(), "Edge".to_string())
342 .list()
343 .required(),
344 );
345
346 self.types.insert("Workflow".to_string(), workflow_type);
347 }
348
349 fn generate_metadata_type(&mut self) {
351 let mut metadata_type =
352 GraphQLType::new("WorkflowMetadata".to_string(), GraphQLTypeKind::Object);
353
354 if self.include_descriptions {
355 metadata_type =
356 metadata_type.with_description("Workflow metadata and description".to_string());
357 }
358
359 metadata_type
360 .add_field(GraphQLField::new("name".to_string(), "String".to_string()).required());
361
362 metadata_type.add_field(GraphQLField::new(
363 "description".to_string(),
364 "String".to_string(),
365 ));
366
367 metadata_type
368 .add_field(GraphQLField::new("version".to_string(), "String".to_string()).required());
369
370 metadata_type.add_field(
371 GraphQLField::new("createdAt".to_string(), "DateTime".to_string()).required(),
372 );
373
374 metadata_type.add_field(
375 GraphQLField::new("updatedAt".to_string(), "DateTime".to_string()).required(),
376 );
377
378 metadata_type.add_field(
379 GraphQLField::new("tags".to_string(), "String".to_string())
380 .list()
381 .required(),
382 );
383
384 self.types
385 .insert("WorkflowMetadata".to_string(), metadata_type);
386 }
387
388 fn generate_node_type(&mut self) {
390 let mut node_type = GraphQLType::new("Node".to_string(), GraphQLTypeKind::Object);
391
392 if self.include_descriptions {
393 node_type = node_type.with_description("A workflow node".to_string());
394 }
395
396 node_type.add_field(GraphQLField::new("id".to_string(), "ID".to_string()).required());
397
398 node_type.add_field(GraphQLField::new("name".to_string(), "String".to_string()).required());
399
400 node_type
401 .add_field(GraphQLField::new("kind".to_string(), "NodeKind".to_string()).required());
402
403 self.types.insert("Node".to_string(), node_type);
404 }
405
406 fn generate_edge_type(&mut self) {
408 let mut edge_type = GraphQLType::new("Edge".to_string(), GraphQLTypeKind::Object);
409
410 if self.include_descriptions {
411 edge_type = edge_type.with_description("An edge connecting two nodes".to_string());
412 }
413
414 edge_type.add_field(GraphQLField::new("id".to_string(), "ID".to_string()).required());
415
416 edge_type.add_field(GraphQLField::new("from".to_string(), "ID".to_string()).required());
417
418 edge_type.add_field(GraphQLField::new("to".to_string(), "ID".to_string()).required());
419
420 self.types.insert("Edge".to_string(), edge_type);
421 }
422
423 fn generate_node_kind_enum(&mut self) {
425 let mut node_kind = GraphQLType::new("NodeKind".to_string(), GraphQLTypeKind::Enum);
426
427 if self.include_descriptions {
428 node_kind = node_kind.with_description("Types of nodes in a workflow".to_string());
429 }
430
431 node_kind.add_enum_value("START".to_string());
432 node_kind.add_enum_value("END".to_string());
433 node_kind.add_enum_value("LLM".to_string());
434 node_kind.add_enum_value("RETRIEVER".to_string());
435 node_kind.add_enum_value("CODE".to_string());
436 node_kind.add_enum_value("IF_ELSE".to_string());
437 node_kind.add_enum_value("TOOL".to_string());
438 node_kind.add_enum_value("LOOP".to_string());
439 node_kind.add_enum_value("TRY_CATCH".to_string());
440 node_kind.add_enum_value("SUB_WORKFLOW".to_string());
441 node_kind.add_enum_value("SWITCH".to_string());
442 node_kind.add_enum_value("PARALLEL".to_string());
443 node_kind.add_enum_value("APPROVAL".to_string());
444 node_kind.add_enum_value("FORM".to_string());
445
446 self.types.insert("NodeKind".to_string(), node_kind);
447 }
448
449 fn generate_execution_state_enum(&mut self) {
451 let mut exec_state = GraphQLType::new("ExecutionState".to_string(), GraphQLTypeKind::Enum);
452
453 if self.include_descriptions {
454 exec_state = exec_state.with_description("State of a workflow execution".to_string());
455 }
456
457 exec_state.add_enum_value("PENDING".to_string());
458 exec_state.add_enum_value("RUNNING".to_string());
459 exec_state.add_enum_value("COMPLETED".to_string());
460 exec_state.add_enum_value("FAILED".to_string());
461 exec_state.add_enum_value("CANCELLED".to_string());
462
463 self.types.insert("ExecutionState".to_string(), exec_state);
464 }
465
466 fn generate_query_type(&mut self) {
468 let mut query_type = GraphQLType::new("Query".to_string(), GraphQLTypeKind::Object);
469
470 let mut get_workflow = GraphQLField::new("workflow".to_string(), "Workflow".to_string());
472 get_workflow
473 .add_argument(GraphQLArgument::new("id".to_string(), "ID".to_string()).required());
474 query_type.add_field(get_workflow);
475
476 let mut list_workflows = GraphQLField::new("workflows".to_string(), "Workflow".to_string())
478 .list()
479 .required();
480 list_workflows.add_argument(
481 GraphQLArgument::new("limit".to_string(), "Int".to_string())
482 .with_default("10".to_string()),
483 );
484 list_workflows.add_argument(
485 GraphQLArgument::new("offset".to_string(), "Int".to_string())
486 .with_default("0".to_string()),
487 );
488 query_type.add_field(list_workflows);
489
490 self.types.insert("Query".to_string(), query_type);
491 }
492
493 fn generate_mutation_type(&mut self) {
495 let mut mutation_type = GraphQLType::new("Mutation".to_string(), GraphQLTypeKind::Object);
496
497 let mut create_workflow =
499 GraphQLField::new("createWorkflow".to_string(), "Workflow".to_string()).required();
500 create_workflow.add_argument(
501 GraphQLArgument::new("name".to_string(), "String".to_string()).required(),
502 );
503 create_workflow.add_argument(GraphQLArgument::new(
504 "description".to_string(),
505 "String".to_string(),
506 ));
507 mutation_type.add_field(create_workflow);
508
509 let mut delete_workflow =
511 GraphQLField::new("deleteWorkflow".to_string(), "Boolean".to_string()).required();
512 delete_workflow
513 .add_argument(GraphQLArgument::new("id".to_string(), "ID".to_string()).required());
514 mutation_type.add_field(delete_workflow);
515
516 self.types.insert("Mutation".to_string(), mutation_type);
517 }
518
519 pub fn to_sdl(&self) -> String {
521 let mut sdl = Vec::new();
522
523 sdl.push("scalar DateTime".to_string());
525 sdl.push("".to_string());
526
527 let type_order = vec![
529 "NodeKind",
530 "ExecutionState",
531 "Edge",
532 "Node",
533 "WorkflowMetadata",
534 "Workflow",
535 "Query",
536 "Mutation",
537 ];
538
539 for type_name in type_order {
540 if let Some(gql_type) = self.types.get(type_name) {
541 sdl.push(gql_type.to_sdl());
542 sdl.push("".to_string());
543 }
544 }
545
546 sdl.join("\n")
547 }
548}
549
550impl Default for GraphQLSchemaGenerator {
551 fn default() -> Self {
552 Self::new()
553 }
554}
555
556pub fn generate_graphql_schema() -> String {
558 let mut generator = GraphQLSchemaGenerator::new();
559 generator.generate_workflow_schema()
560}
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565
566 #[test]
567 fn test_graphql_field_sdl() {
568 let field = GraphQLField::new("name".to_string(), "String".to_string()).required();
569 assert_eq!(field.to_sdl(), "name: String!");
570 }
571
572 #[test]
573 fn test_graphql_field_list() {
574 let field = GraphQLField::new("tags".to_string(), "String".to_string())
575 .list()
576 .required();
577 assert_eq!(field.to_sdl(), "tags: [String]!");
578 }
579
580 #[test]
581 fn test_graphql_argument() {
582 let arg = GraphQLArgument::new("id".to_string(), "ID".to_string()).required();
583 assert_eq!(arg.to_sdl(), "id: ID!");
584 }
585
586 #[test]
587 fn test_graphql_argument_with_default() {
588 let arg = GraphQLArgument::new("limit".to_string(), "Int".to_string())
589 .with_default("10".to_string());
590 assert_eq!(arg.to_sdl(), "limit: Int = 10");
591 }
592
593 #[test]
594 fn test_enum_type_sdl() {
595 let mut enum_type = GraphQLType::new("Status".to_string(), GraphQLTypeKind::Enum);
596 enum_type.add_enum_value("ACTIVE".to_string());
597 enum_type.add_enum_value("INACTIVE".to_string());
598
599 let sdl = enum_type.to_sdl();
600 assert!(sdl.contains("enum Status"));
601 assert!(sdl.contains("ACTIVE"));
602 assert!(sdl.contains("INACTIVE"));
603 }
604
605 #[test]
606 fn test_object_type_sdl() {
607 let mut object_type = GraphQLType::new("User".to_string(), GraphQLTypeKind::Object);
608 object_type.add_field(GraphQLField::new("id".to_string(), "ID".to_string()).required());
609 object_type
610 .add_field(GraphQLField::new("name".to_string(), "String".to_string()).required());
611
612 let sdl = object_type.to_sdl();
613 assert!(sdl.contains("type User"));
614 assert!(sdl.contains("id: ID!"));
615 assert!(sdl.contains("name: String!"));
616 }
617
618 #[test]
619 fn test_generate_workflow_schema() {
620 let mut generator = GraphQLSchemaGenerator::new();
621 let schema = generator.generate_workflow_schema();
622
623 assert!(schema.contains("type Workflow"));
624 assert!(schema.contains("type Node"));
625 assert!(schema.contains("type Edge"));
626 assert!(schema.contains("enum NodeKind"));
627 assert!(schema.contains("type Query"));
628 assert!(schema.contains("type Mutation"));
629 }
630
631 #[test]
632 fn test_schema_has_node_kinds() {
633 let mut generator = GraphQLSchemaGenerator::new();
634 let schema = generator.generate_workflow_schema();
635
636 assert!(schema.contains("START"));
637 assert!(schema.contains("END"));
638 assert!(schema.contains("LLM"));
639 assert!(schema.contains("RETRIEVER"));
640 }
641
642 #[test]
643 fn test_schema_has_execution_states() {
644 let mut generator = GraphQLSchemaGenerator::new();
645 let schema = generator.generate_workflow_schema();
646
647 assert!(schema.contains("PENDING"));
648 assert!(schema.contains("RUNNING"));
649 assert!(schema.contains("COMPLETED"));
650 assert!(schema.contains("FAILED"));
651 }
652
653 #[test]
654 fn test_query_type_has_workflow_field() {
655 let mut generator = GraphQLSchemaGenerator::new();
656 generator.generate_query_type();
657
658 if let Some(query_type) = generator.types.get("Query") {
659 assert!(query_type.fields.iter().any(|f| f.name == "workflow"));
660 assert!(query_type.fields.iter().any(|f| f.name == "workflows"));
661 } else {
662 panic!("Query type not found");
663 }
664 }
665
666 #[test]
667 fn test_mutation_type_has_create_workflow() {
668 let mut generator = GraphQLSchemaGenerator::new();
669 generator.generate_mutation_type();
670
671 if let Some(mutation_type) = generator.types.get("Mutation") {
672 assert!(mutation_type
673 .fields
674 .iter()
675 .any(|f| f.name == "createWorkflow"));
676 assert!(mutation_type
677 .fields
678 .iter()
679 .any(|f| f.name == "deleteWorkflow"));
680 } else {
681 panic!("Mutation type not found");
682 }
683 }
684
685 #[test]
686 fn test_field_with_description() {
687 let field = GraphQLField::new("name".to_string(), "String".to_string())
688 .with_description("The name of the user".to_string())
689 .required();
690
691 let sdl = field.to_sdl();
692 assert!(sdl.contains("The name of the user"));
693 }
694
695 #[test]
696 fn test_type_with_description() {
697 let type_def = GraphQLType::new("User".to_string(), GraphQLTypeKind::Object)
698 .with_description("A user in the system".to_string());
699
700 let sdl = type_def.to_sdl();
701 assert!(sdl.contains("A user in the system"));
702 }
703
704 #[test]
705 fn test_field_with_arguments() {
706 let mut field = GraphQLField::new("users".to_string(), "User".to_string()).list();
707 field.add_argument(GraphQLArgument::new("limit".to_string(), "Int".to_string()));
708 field.add_argument(GraphQLArgument::new(
709 "offset".to_string(),
710 "Int".to_string(),
711 ));
712
713 let sdl = field.to_sdl();
714 assert!(sdl.contains("users("));
715 assert!(sdl.contains("limit: Int"));
716 assert!(sdl.contains("offset: Int"));
717 }
718}