1use std::collections::HashMap;
41
42use serde::{Deserialize, Serialize};
43
44use crate::{
45 compiler::fact_table::FactTableMetadata, schema::GraphQLValue, validation::ValidationRule,
46};
47
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub struct AuthoringIR {
54 pub types: Vec<IRType>,
56
57 #[serde(default)]
59 pub enums: Vec<IREnum>,
60
61 #[serde(default)]
63 pub interfaces: Vec<IRInterface>,
64
65 #[serde(default)]
67 pub unions: Vec<IRUnion>,
68
69 #[serde(default)]
71 pub input_types: Vec<IRInputType>,
72
73 #[serde(default)]
75 pub scalars: Vec<IRScalar>,
76
77 pub queries: Vec<IRQuery>,
79
80 pub mutations: Vec<IRMutation>,
82
83 pub subscriptions: Vec<IRSubscription>,
85
86 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
89 pub fact_tables: HashMap<String, FactTableMetadata>,
90}
91
92impl AuthoringIR {
93 #[must_use]
95 pub fn new() -> Self {
96 Self {
97 types: Vec::new(),
98 enums: Vec::new(),
99 interfaces: Vec::new(),
100 unions: Vec::new(),
101 input_types: Vec::new(),
102 scalars: Vec::new(),
103 queries: Vec::new(),
104 mutations: Vec::new(),
105 subscriptions: Vec::new(),
106 fact_tables: HashMap::new(),
107 }
108 }
109}
110
111impl Default for AuthoringIR {
112 fn default() -> Self {
113 Self::new()
114 }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
119pub struct IRType {
120 pub name: String,
122
123 pub fields: Vec<IRField>,
125
126 pub sql_source: Option<String>,
128
129 pub description: Option<String>,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
135pub struct IRField {
136 pub name: String,
138
139 pub field_type: String,
141
142 pub nullable: bool,
144
145 pub description: Option<String>,
147
148 pub sql_column: Option<String>,
150}
151
152#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
154pub struct IRQuery {
155 pub name: String,
157
158 pub return_type: String,
160
161 pub returns_list: bool,
163
164 pub nullable: bool,
166
167 pub arguments: Vec<IRArgument>,
169
170 pub sql_source: Option<String>,
172
173 pub description: Option<String>,
175
176 pub auto_params: AutoParams,
178}
179
180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub struct IRMutation {
183 pub name: String,
185
186 pub return_type: String,
188
189 pub nullable: bool,
191
192 pub arguments: Vec<IRArgument>,
194
195 pub description: Option<String>,
197
198 pub operation: MutationOperation,
200}
201
202#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
204pub struct IRSubscription {
205 pub name: String,
207
208 pub return_type: String,
210
211 pub arguments: Vec<IRArgument>,
213
214 pub description: Option<String>,
216}
217
218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
220pub struct IRArgument {
221 pub name: String,
223
224 pub arg_type: String,
226
227 pub nullable: bool,
229
230 pub default_value: Option<GraphQLValue>,
232
233 pub description: Option<String>,
235}
236
237#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
239pub struct AutoParams {
240 #[serde(default)]
242 pub has_where: bool,
243
244 #[serde(default)]
246 pub has_order_by: bool,
247
248 #[serde(default)]
250 pub has_limit: bool,
251
252 #[serde(default)]
254 pub has_offset: bool,
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
259#[non_exhaustive]
260pub enum MutationOperation {
261 Create,
263
264 Update,
266
267 Delete,
269
270 Custom,
272}
273
274#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
276pub struct IREnum {
277 pub name: String,
279
280 pub values: Vec<IREnumValue>,
282
283 pub description: Option<String>,
285}
286
287#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
289pub struct IREnumValue {
290 pub name: String,
292
293 pub description: Option<String>,
295
296 pub deprecation_reason: Option<String>,
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
302pub struct IRInterface {
303 pub name: String,
305
306 pub fields: Vec<IRField>,
308
309 pub description: Option<String>,
311}
312
313#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
315pub struct IRUnion {
316 pub name: String,
318
319 pub types: Vec<String>,
321
322 pub description: Option<String>,
324}
325
326#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
328pub struct IRInputType {
329 pub name: String,
331
332 pub fields: Vec<IRInputField>,
334
335 pub description: Option<String>,
337}
338
339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
341pub struct IRInputField {
342 pub name: String,
344
345 pub field_type: String,
347
348 pub nullable: bool,
350
351 pub default_value: Option<GraphQLValue>,
353
354 pub description: Option<String>,
356}
357
358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
364pub struct IRScalar {
365 pub name: String,
367
368 pub description: Option<String>,
370
371 pub specified_by_url: Option<String>,
374
375 #[serde(default)]
377 pub validation_rules: Vec<ValidationRule>,
378
379 pub base_type: Option<String>,
381}
382
383impl IRScalar {
384 #[must_use]
386 pub const fn new(name: String) -> Self {
387 Self {
388 name,
389 description: None,
390 specified_by_url: None,
391 validation_rules: Vec::new(),
392 base_type: None,
393 }
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_authoring_ir_new() {
403 let ir = AuthoringIR::new();
404 assert!(ir.types.is_empty());
405 assert!(ir.queries.is_empty());
406 assert!(ir.mutations.is_empty());
407 assert!(ir.subscriptions.is_empty());
408 }
409
410 #[test]
411 fn test_authoring_ir_with_scalars() {
412 let mut ir = AuthoringIR::new();
413
414 ir.scalars.push(IRScalar::new("Email".to_string()));
416 ir.scalars.push(IRScalar::new("ISBN".to_string()));
417
418 assert_eq!(ir.scalars.len(), 2);
419 assert_eq!(ir.scalars[0].name, "Email");
420 assert_eq!(ir.scalars[1].name, "ISBN");
421 }
422
423 #[test]
424 fn test_ir_type() {
425 let ir_type = IRType {
426 name: "User".to_string(),
427 fields: vec![IRField {
428 name: "id".to_string(),
429 field_type: "Int!".to_string(),
430 nullable: false,
431 description: None,
432 sql_column: Some("id".to_string()),
433 }],
434 sql_source: Some("v_user".to_string()),
435 description: Some("User type".to_string()),
436 };
437
438 assert_eq!(ir_type.name, "User");
439 assert_eq!(ir_type.fields.len(), 1);
440 assert_eq!(ir_type.sql_source, Some("v_user".to_string()));
441 }
442
443 #[test]
444 fn test_ir_query() {
445 let query = IRQuery {
446 name: "users".to_string(),
447 return_type: "User".to_string(),
448 returns_list: true,
449 nullable: false,
450 arguments: vec![],
451 sql_source: Some("v_user".to_string()),
452 description: None,
453 auto_params: AutoParams {
454 has_where: true,
455 has_limit: true,
456 ..Default::default()
457 },
458 };
459
460 assert_eq!(query.name, "users");
461 assert!(query.returns_list);
462 assert!(query.auto_params.has_where);
463 assert!(query.auto_params.has_limit);
464 }
465
466 #[test]
467 fn test_ir_mutation() {
468 let mutation = IRMutation {
469 name: "createUser".to_string(),
470 return_type: "User".to_string(),
471 nullable: false,
472 arguments: vec![IRArgument {
473 name: "input".to_string(),
474 arg_type: "CreateUserInput!".to_string(),
475 nullable: false,
476 default_value: None,
477 description: None,
478 }],
479 description: None,
480 operation: MutationOperation::Create,
481 };
482
483 assert_eq!(mutation.name, "createUser");
484 assert_eq!(mutation.operation, MutationOperation::Create);
485 assert_eq!(mutation.arguments.len(), 1);
486 }
487
488 #[test]
489 fn test_auto_params_default() {
490 let params = AutoParams::default();
491 assert!(!params.has_where);
492 assert!(!params.has_order_by);
493 assert!(!params.has_limit);
494 assert!(!params.has_offset);
495 }
496
497 #[test]
498 fn test_mutation_operations() {
499 assert_eq!(MutationOperation::Create, MutationOperation::Create);
500 assert_ne!(MutationOperation::Create, MutationOperation::Update);
501 }
502
503 #[test]
504 fn test_ir_scalar_new() {
505 let scalar = IRScalar::new("Email".to_string());
506
507 assert_eq!(scalar.name, "Email");
508 assert_eq!(scalar.description, None);
509 assert_eq!(scalar.specified_by_url, None);
510 assert_eq!(scalar.validation_rules.len(), 0);
511 assert_eq!(scalar.base_type, None);
512 }
513
514 #[test]
515 fn test_ir_scalar_with_all_fields() {
516 let scalar = IRScalar {
517 name: "Email".to_string(),
518 description: Some("Valid email address".to_string()),
519 specified_by_url: Some("https://html.spec.whatwg.org/".to_string()),
520 validation_rules: vec![ValidationRule::Pattern {
521 pattern: r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$".to_string(),
522 message: Some("Invalid email format".to_string()),
523 }],
524 base_type: Some("String".to_string()),
525 };
526
527 assert_eq!(scalar.name, "Email");
528 assert_eq!(scalar.description, Some("Valid email address".to_string()));
529 assert_eq!(scalar.specified_by_url, Some("https://html.spec.whatwg.org/".to_string()));
530 assert_eq!(scalar.validation_rules.len(), 1);
531 assert_eq!(scalar.base_type, Some("String".to_string()));
532 }
533
534 #[test]
535 fn test_ir_scalar_serialization() {
536 let scalar = IRScalar {
537 name: "ISBN".to_string(),
538 description: Some("International Standard Book Number".to_string()),
539 specified_by_url: Some("https://www.isbn-international.org/".to_string()),
540 validation_rules: vec![],
541 base_type: None,
542 };
543
544 let json = serde_json::to_value(&scalar).expect("Should serialize");
546
547 assert_eq!(json["name"], "ISBN");
549 assert_eq!(json["description"], "International Standard Book Number");
550 assert_eq!(json["specified_by_url"], "https://www.isbn-international.org/");
551 assert_eq!(json["validation_rules"], serde_json::json!([]));
552 }
553
554 #[test]
555 fn test_ir_scalar_deserialization() {
556 let json = serde_json::json!({
557 "name": "PhoneNumber",
558 "description": "Valid phone number",
559 "specified_by_url": null,
560 "validation_rules": [],
561 "base_type": "String"
562 });
563
564 let scalar: IRScalar = serde_json::from_value(json).expect("Should deserialize");
565
566 assert_eq!(scalar.name, "PhoneNumber");
567 assert_eq!(scalar.description, Some("Valid phone number".to_string()));
568 assert_eq!(scalar.specified_by_url, None);
569 assert_eq!(scalar.validation_rules.len(), 0);
570 assert_eq!(scalar.base_type, Some("String".to_string()));
571 }
572
573 #[test]
574 fn test_ir_scalar_equality() {
575 let scalar1 = IRScalar {
576 name: "UUID".to_string(),
577 description: Some("Universal Unique Identifier".to_string()),
578 specified_by_url: None,
579 validation_rules: vec![],
580 base_type: None,
581 };
582
583 let scalar2 = IRScalar {
584 name: "UUID".to_string(),
585 description: Some("Universal Unique Identifier".to_string()),
586 specified_by_url: None,
587 validation_rules: vec![],
588 base_type: None,
589 };
590
591 assert_eq!(scalar1, scalar2);
592 }
593}