1use serde::{Deserialize, Serialize};
35use smol_str::SmolStr;
36
37use super::Span;
38
39#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
41pub struct GraphQLConfig {
42 pub name: Option<String>,
44 pub description: Option<String>,
46 pub skip: bool,
48 pub is_interface: bool,
50 pub union_types: Vec<String>,
52 pub implements: Vec<String>,
54 pub directives: Vec<GraphQLDirective>,
56 pub complexity: Option<u32>,
58 pub guard: Option<String>,
60 pub generate_input: bool,
62 pub generate_filter: bool,
64 pub generate_order: bool,
66 pub resolver: Option<String>,
68}
69
70impl GraphQLConfig {
71 pub fn new() -> Self {
73 Self {
74 generate_input: true,
75 generate_filter: true,
76 generate_order: true,
77 ..Default::default()
78 }
79 }
80
81 pub fn with_name(mut self, name: impl Into<String>) -> Self {
83 self.name = Some(name.into());
84 self
85 }
86
87 pub fn skip(mut self) -> Self {
89 self.skip = true;
90 self
91 }
92
93 pub fn as_interface(mut self) -> Self {
95 self.is_interface = true;
96 self
97 }
98
99 pub fn in_union(mut self, union_name: impl Into<String>) -> Self {
101 self.union_types.push(union_name.into());
102 self
103 }
104
105 pub fn implements(mut self, interface: impl Into<String>) -> Self {
107 self.implements.push(interface.into());
108 self
109 }
110
111 pub fn with_complexity(mut self, complexity: u32) -> Self {
113 self.complexity = Some(complexity);
114 self
115 }
116}
117
118#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
120pub struct GraphQLFieldConfig {
121 pub name: Option<String>,
123 pub description: Option<String>,
125 pub skip: bool,
127 pub deprecation: Option<String>,
129 pub complexity: Option<ComplexityConfig>,
131 pub guard: Option<String>,
133 pub resolver: Option<String>,
135 pub directives: Vec<GraphQLDirective>,
137 pub derived: bool,
139 pub shareable: bool,
141 pub external: bool,
143 pub provides: Option<String>,
145 pub requires: Option<String>,
147 pub key: bool,
149}
150
151impl GraphQLFieldConfig {
152 pub fn new() -> Self {
154 Self::default()
155 }
156
157 pub fn with_name(mut self, name: impl Into<String>) -> Self {
159 self.name = Some(name.into());
160 self
161 }
162
163 pub fn skip(mut self) -> Self {
165 self.skip = true;
166 self
167 }
168
169 pub fn deprecated(mut self, reason: impl Into<String>) -> Self {
171 self.deprecation = Some(reason.into());
172 self
173 }
174
175 pub fn derived(mut self) -> Self {
177 self.derived = true;
178 self
179 }
180}
181
182#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub enum ComplexityConfig {
185 Fixed(u32),
187 Multiplier {
189 base: u32,
191 argument: String,
193 },
194 Custom(String),
196}
197
198impl Default for ComplexityConfig {
199 fn default() -> Self {
200 Self::Fixed(1)
201 }
202}
203
204#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
206pub struct GraphQLDirective {
207 pub name: SmolStr,
209 pub arguments: Vec<GraphQLArgument>,
211 pub span: Span,
213}
214
215impl GraphQLDirective {
216 pub fn new(name: impl Into<SmolStr>, span: Span) -> Self {
218 Self {
219 name: name.into(),
220 arguments: Vec::new(),
221 span,
222 }
223 }
224
225 pub fn with_arg(mut self, name: impl Into<SmolStr>, value: GraphQLValue) -> Self {
227 self.arguments.push(GraphQLArgument {
228 name: name.into(),
229 value,
230 });
231 self
232 }
233
234 pub fn to_sdl(&self) -> String {
236 if self.arguments.is_empty() {
237 format!("@{}", self.name)
238 } else {
239 let args: Vec<String> = self
240 .arguments
241 .iter()
242 .map(|a| format!("{}: {}", a.name, a.value.to_sdl()))
243 .collect();
244 format!("@{}({})", self.name, args.join(", "))
245 }
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
251pub struct GraphQLArgument {
252 pub name: SmolStr,
254 pub value: GraphQLValue,
256}
257
258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
260pub enum GraphQLValue {
261 String(String),
263 Int(i64),
265 Float(f64),
267 Boolean(bool),
269 Enum(String),
271 List(Vec<GraphQLValue>),
273 Object(Vec<(String, GraphQLValue)>),
275 Null,
277}
278
279impl GraphQLValue {
280 pub fn to_sdl(&self) -> String {
282 match self {
283 Self::String(s) => format!("\"{}\"", s.replace('"', "\\\"")),
284 Self::Int(i) => i.to_string(),
285 Self::Float(f) => f.to_string(),
286 Self::Boolean(b) => b.to_string(),
287 Self::Enum(e) => e.clone(),
288 Self::List(items) => {
289 let vals: Vec<String> = items.iter().map(|v| v.to_sdl()).collect();
290 format!("[{}]", vals.join(", "))
291 }
292 Self::Object(fields) => {
293 let field_strs: Vec<String> = fields
294 .iter()
295 .map(|(k, v)| format!("{}: {}", k, v.to_sdl()))
296 .collect();
297 format!("{{{}}}", field_strs.join(", "))
298 }
299 Self::Null => "null".to_string(),
300 }
301 }
302}
303
304#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
306pub struct SubscriptionConfig {
307 pub enabled: bool,
309 pub on_create: bool,
311 pub on_update: bool,
313 pub on_delete: bool,
315 pub filter: Option<String>,
317}
318
319impl SubscriptionConfig {
320 pub fn all() -> Self {
322 Self {
323 enabled: true,
324 on_create: true,
325 on_update: true,
326 on_delete: true,
327 filter: None,
328 }
329 }
330
331 pub fn only_create() -> Self {
333 Self {
334 enabled: true,
335 on_create: true,
336 ..Default::default()
337 }
338 }
339}
340
341#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
343pub struct FederationConfig {
344 pub is_entity: bool,
346 pub keys: Vec<FederationKey>,
348 pub shareable: bool,
350 pub external: bool,
352 pub extends: bool,
354 pub interface_object: bool,
356}
357
358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
360pub struct FederationKey {
361 pub fields: String,
363 pub resolvable: bool,
365}
366
367impl FederationKey {
368 pub fn new(fields: impl Into<String>) -> Self {
370 Self {
371 fields: fields.into(),
372 resolvable: true,
373 }
374 }
375
376 pub fn non_resolvable(mut self) -> Self {
378 self.resolvable = false;
379 self
380 }
381
382 pub fn to_sdl(&self) -> String {
384 if self.resolvable {
385 format!("@key(fields: \"{}\")", self.fields)
386 } else {
387 format!("@key(fields: \"{}\", resolvable: false)", self.fields)
388 }
389 }
390}
391
392pub fn parse_graphql_config_from_tags(tags: &[super::validation::DocTag]) -> GraphQLConfig {
394 let mut config = GraphQLConfig::new();
395
396 for tag in tags {
397 match tag.name.as_str() {
398 "graphql.skip" | "graphql_skip" => config.skip = true,
399 "graphql.name" | "graphql_name" => config.name = tag.value.clone(),
400 "graphql.interface" | "graphql_interface" => config.is_interface = true,
401 "graphql.union" | "graphql_union" => {
402 if let Some(v) = &tag.value {
403 config.union_types.push(v.clone());
404 }
405 }
406 "graphql.implements" | "graphql_implements" => {
407 if let Some(v) = &tag.value {
408 config.implements.push(v.clone());
409 }
410 }
411 "graphql.complexity" | "graphql_complexity" => {
412 if let Some(v) = &tag.value {
413 config.complexity = v.parse().ok();
414 }
415 }
416 "graphql.guard" | "graphql_guard" => config.guard = tag.value.clone(),
417 "graphql.resolver" | "graphql_resolver" => config.resolver = tag.value.clone(),
418 "graphql.no_input" | "graphql_no_input" => config.generate_input = false,
419 "graphql.no_filter" | "graphql_no_filter" => config.generate_filter = false,
420 "graphql.no_order" | "graphql_no_order" => config.generate_order = false,
421 _ => {}
422 }
423 }
424
425 config
426}
427
428pub fn parse_graphql_field_config_from_tags(
430 tags: &[super::validation::DocTag],
431) -> GraphQLFieldConfig {
432 let mut config = GraphQLFieldConfig::new();
433
434 for tag in tags {
435 match tag.name.as_str() {
436 "graphql.skip" | "graphql_skip" => config.skip = true,
437 "graphql.name" | "graphql_name" => config.name = tag.value.clone(),
438 "graphql.deprecation" | "graphql_deprecation" | "deprecated" => {
439 config.deprecation = tag.value.clone().or(Some(String::new()));
440 }
441 "graphql.guard" | "graphql_guard" => config.guard = tag.value.clone(),
442 "graphql.resolver" | "graphql_resolver" => config.resolver = tag.value.clone(),
443 "graphql.derived" | "graphql_derived" => config.derived = true,
444 "graphql.shareable" | "graphql_shareable" => config.shareable = true,
445 "graphql.external" | "graphql_external" => config.external = true,
446 "graphql.provides" | "graphql_provides" => config.provides = tag.value.clone(),
447 "graphql.requires" | "graphql_requires" => config.requires = tag.value.clone(),
448 "graphql.key" | "graphql_key" => config.key = true,
449 _ => {}
450 }
451 }
452
453 config
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn test_graphql_config_builder() {
462 let config = GraphQLConfig::new()
463 .with_name("CustomUser")
464 .as_interface()
465 .in_union("SearchResult")
466 .implements("Node")
467 .with_complexity(10);
468
469 assert_eq!(config.name, Some("CustomUser".to_string()));
470 assert!(config.is_interface);
471 assert_eq!(config.union_types, vec!["SearchResult"]);
472 assert_eq!(config.implements, vec!["Node"]);
473 assert_eq!(config.complexity, Some(10));
474 }
475
476 #[test]
477 fn test_graphql_field_config() {
478 let config = GraphQLFieldConfig::new()
479 .with_name("userId")
480 .deprecated("Use newId instead")
481 .derived();
482
483 assert_eq!(config.name, Some("userId".to_string()));
484 assert_eq!(config.deprecation, Some("Use newId instead".to_string()));
485 assert!(config.derived);
486 }
487
488 #[test]
489 fn test_graphql_directive_sdl() {
490 let directive = GraphQLDirective::new("deprecated", Span::new(0, 0))
491 .with_arg("reason", GraphQLValue::String("Use newField".to_string()));
492
493 assert_eq!(directive.to_sdl(), "@deprecated(reason: \"Use newField\")");
494
495 let simple_directive = GraphQLDirective::new("shareable", Span::new(0, 0));
496 assert_eq!(simple_directive.to_sdl(), "@shareable");
497 }
498
499 #[test]
500 fn test_graphql_value_sdl() {
501 assert_eq!(
502 GraphQLValue::String("hello".to_string()).to_sdl(),
503 "\"hello\""
504 );
505 assert_eq!(GraphQLValue::Int(42).to_sdl(), "42");
506 assert_eq!(GraphQLValue::Float(3.14).to_sdl(), "3.14");
507 assert_eq!(GraphQLValue::Boolean(true).to_sdl(), "true");
508 assert_eq!(GraphQLValue::Enum("ADMIN".to_string()).to_sdl(), "ADMIN");
509 assert_eq!(GraphQLValue::Null.to_sdl(), "null");
510
511 let list = GraphQLValue::List(vec![
512 GraphQLValue::Int(1),
513 GraphQLValue::Int(2),
514 GraphQLValue::Int(3),
515 ]);
516 assert_eq!(list.to_sdl(), "[1, 2, 3]");
517
518 let obj = GraphQLValue::Object(vec![
519 ("name".to_string(), GraphQLValue::String("test".to_string())),
520 ("count".to_string(), GraphQLValue::Int(5)),
521 ]);
522 assert_eq!(obj.to_sdl(), "{name: \"test\", count: 5}");
523 }
524
525 #[test]
526 fn test_federation_key_sdl() {
527 let key = FederationKey::new("id");
528 assert_eq!(key.to_sdl(), "@key(fields: \"id\")");
529
530 let composite_key = FederationKey::new("userId organizationId");
531 assert_eq!(
532 composite_key.to_sdl(),
533 "@key(fields: \"userId organizationId\")"
534 );
535
536 let non_resolvable = FederationKey::new("id").non_resolvable();
537 assert_eq!(
538 non_resolvable.to_sdl(),
539 "@key(fields: \"id\", resolvable: false)"
540 );
541 }
542
543 #[test]
544 fn test_subscription_config() {
545 let all = SubscriptionConfig::all();
546 assert!(all.enabled);
547 assert!(all.on_create);
548 assert!(all.on_update);
549 assert!(all.on_delete);
550
551 let only_create = SubscriptionConfig::only_create();
552 assert!(only_create.enabled);
553 assert!(only_create.on_create);
554 assert!(!only_create.on_update);
555 assert!(!only_create.on_delete);
556 }
557
558 #[test]
559 fn test_parse_graphql_config_from_tags() {
560 use super::super::validation::DocTag;
561
562 let span = Span::new(0, 0);
563 let tags = vec![
564 DocTag::new("graphql.name", Some("CustomModel".to_string()), span),
565 DocTag::new("graphql.interface", None, span),
566 DocTag::new("graphql.complexity", Some("5".to_string()), span),
567 ];
568
569 let config = parse_graphql_config_from_tags(&tags);
570
571 assert_eq!(config.name, Some("CustomModel".to_string()));
572 assert!(config.is_interface);
573 assert_eq!(config.complexity, Some(5));
574 }
575
576 #[test]
577 fn test_parse_graphql_field_config_from_tags() {
578 use super::super::validation::DocTag;
579
580 let span = Span::new(0, 0);
581 let tags = vec![
582 DocTag::new("graphql.name", Some("userId".to_string()), span),
583 DocTag::new("deprecated", Some("Use newId".to_string()), span),
584 DocTag::new("graphql.shareable", None, span),
585 ];
586
587 let config = parse_graphql_field_config_from_tags(&tags);
588
589 assert_eq!(config.name, Some("userId".to_string()));
590 assert_eq!(config.deprecation, Some("Use newId".to_string()));
591 assert!(config.shareable);
592 }
593}