fraiseql_core/schema/compiled/argument.rs
1use serde::{Deserialize, Serialize};
2
3use crate::schema::{
4 field_type::{DeprecationInfo, FieldType},
5 graphql_value::GraphQLValue,
6};
7
8/// Query/mutation/subscription argument definition.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct ArgumentDefinition {
11 /// Argument name.
12 pub name: String,
13
14 /// Argument type.
15 pub arg_type: FieldType,
16
17 /// Is this argument optional?
18 #[serde(default)]
19 pub nullable: bool,
20
21 /// Default value.
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub default_value: Option<GraphQLValue>,
24
25 /// Description.
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub description: Option<String>,
28
29 /// Deprecation information (from @deprecated directive).
30 /// When set, this argument is marked as deprecated in the schema.
31 /// Per GraphQL spec, deprecated arguments should still be accepted but
32 /// clients are encouraged to migrate to alternatives.
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub deprecation: Option<DeprecationInfo>,
35}
36
37impl ArgumentDefinition {
38 /// Create a new required argument.
39 #[must_use]
40 pub fn new(name: impl Into<String>, arg_type: FieldType) -> Self {
41 Self {
42 name: name.into(),
43 arg_type,
44 nullable: false,
45 default_value: None,
46 description: None,
47 deprecation: None,
48 }
49 }
50
51 /// Create a new optional argument.
52 #[must_use]
53 pub fn optional(name: impl Into<String>, arg_type: FieldType) -> Self {
54 Self {
55 name: name.into(),
56 arg_type,
57 nullable: true,
58 default_value: None,
59 description: None,
60 deprecation: None,
61 }
62 }
63
64 /// Attach a description to this argument.
65 #[must_use]
66 pub fn with_description(mut self, description: impl Into<String>) -> Self {
67 self.description = Some(description.into());
68 self
69 }
70
71 /// Mark this argument as deprecated.
72 ///
73 /// # Example
74 ///
75 /// ```
76 /// use fraiseql_core::schema::{ArgumentDefinition, FieldType};
77 ///
78 /// let arg = ArgumentDefinition::optional("oldLimit", FieldType::Int)
79 /// .deprecated(Some("Use 'first' instead".to_string()));
80 /// assert!(arg.is_deprecated());
81 /// ```
82 #[must_use]
83 pub fn deprecated(mut self, reason: Option<String>) -> Self {
84 self.deprecation = Some(DeprecationInfo { reason });
85 self
86 }
87
88 /// Check if this argument is deprecated.
89 #[must_use]
90 pub const fn is_deprecated(&self) -> bool {
91 self.deprecation.is_some()
92 }
93
94 /// Get the deprecation reason if deprecated.
95 #[must_use]
96 pub fn deprecation_reason(&self) -> Option<&str> {
97 self.deprecation.as_ref().and_then(|d| d.reason.as_deref())
98 }
99}
100
101/// Auto-wired query parameters.
102///
103/// These are standard parameters automatically added to list queries.
104#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
105#[allow(clippy::struct_excessive_bools)] // Reason: these are intentional feature flags
106pub struct AutoParams {
107 /// Enable `where` filtering.
108 #[serde(default)]
109 pub has_where: bool,
110
111 /// Enable `orderBy` sorting.
112 #[serde(default)]
113 pub has_order_by: bool,
114
115 /// Enable `limit` pagination.
116 #[serde(default)]
117 pub has_limit: bool,
118
119 /// Enable `offset` pagination.
120 #[serde(default)]
121 pub has_offset: bool,
122}
123
124impl AutoParams {
125 /// Create with all auto-params enabled (common for list queries).
126 #[must_use]
127 pub const fn all() -> Self {
128 Self {
129 has_where: true,
130 has_order_by: true,
131 has_limit: true,
132 has_offset: true,
133 }
134 }
135
136 /// Create with no auto-params (common for single-item queries).
137 #[must_use]
138 pub fn none() -> Self {
139 Self::default()
140 }
141
142 /// The shape the Relay spec mandates: filter and sort, but no `limit`/`offset`.
143 ///
144 /// A connection is paged by `first`/`after` — offering `limit` and `offset` beside
145 /// them would be a second, unsynchronised way to move the same cursor. The compiler
146 /// applies this unconditionally to a relay query, ahead of the list defaults
147 /// (`QueryConverter::convert_query`).
148 ///
149 /// Named rather than written out at each site because the shape is authored twice —
150 /// once by the compiler, once by `TestQueryBuilder` — and a fixture that disagrees
151 /// with the compiler about a flag is a test asserting over a parameter the engine
152 /// would have dropped (#1290).
153 #[must_use]
154 pub const fn relay() -> Self {
155 Self {
156 has_where: true,
157 has_order_by: true,
158 has_limit: false,
159 has_offset: false,
160 }
161 }
162}