1use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum GraphQLType {
8 Int,
10 Float,
12 String,
14 Boolean,
16 Id,
18 BigInt,
20 BigDecimal,
22 Json,
24 Uuid,
26 Date,
28 DateTime,
30 Time,
32 List(Box<GraphQLType>),
34 Custom(std::string::String),
36}
37
38impl fmt::Display for GraphQLType {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 GraphQLType::Int => write!(f, "Int"),
42 GraphQLType::Float => write!(f, "Float"),
43 GraphQLType::String => write!(f, "String"),
44 GraphQLType::Boolean => write!(f, "Boolean"),
45 GraphQLType::Id => write!(f, "ID"),
46 GraphQLType::BigInt => write!(f, "bigint"),
51 GraphQLType::BigDecimal => write!(f, "numeric"),
52 GraphQLType::Json => write!(f, "jsonb"),
53 GraphQLType::Uuid => write!(f, "uuid"),
54 GraphQLType::Date => write!(f, "date"),
55 GraphQLType::DateTime => write!(f, "timestamptz"),
56 GraphQLType::Time => write!(f, "time"),
57 GraphQLType::List(inner) => write!(f, "[{}]", inner),
58 GraphQLType::Custom(name) => write!(f, "{}", name),
59 }
60 }
61}
62
63pub fn pg_type_to_graphql(pg_type: &str) -> GraphQLType {
65 let normalized = pg_type.to_lowercase().trim().to_string();
67
68 if let Some(inner_type) = normalized.strip_prefix('_') {
70 return GraphQLType::List(Box::new(pg_type_to_graphql(inner_type)));
72 }
73
74 if normalized.ends_with("[]") {
75 let inner_type = normalized.trim_end_matches("[]");
77 return GraphQLType::List(Box::new(pg_type_to_graphql(inner_type)));
78 }
79
80 match normalized.as_str() {
81 "integer" | "int" | "int4" | "smallint" | "int2" => GraphQLType::Int,
83
84 "bigint" | "int8" => GraphQLType::BigInt,
86
87 "real" | "float4" | "double precision" | "float8" => GraphQLType::Float,
89
90 "numeric" | "decimal" => GraphQLType::BigDecimal,
92
93 "boolean" | "bool" => GraphQLType::Boolean,
95
96 "text" | "varchar" | "character varying" | "char" | "character" | "bpchar" => {
98 GraphQLType::String
99 }
100
101 "jsonb" => GraphQLType::Json,
104 "json" => GraphQLType::Custom("json".to_string()),
105
106 "uuid" => GraphQLType::Uuid,
108
109 "timestamptz" | "timestamp with time zone" => GraphQLType::DateTime,
111 "timestamp" | "timestamp without time zone" => GraphQLType::Custom("timestamp".to_string()),
112 "date" => GraphQLType::Date,
113 "time" | "time without time zone" => GraphQLType::Time,
114 "timetz" | "time with time zone" => GraphQLType::Custom("timetz".to_string()),
115
116 other => GraphQLType::Custom(other.to_string()),
123 }
124}
125
126pub fn is_nullable_type(nullable: bool, is_pk: bool) -> bool {
128 if is_pk {
130 return false;
131 }
132 nullable
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use pretty_assertions::assert_eq;
139
140 #[test]
141 fn test_pg_to_graphql_integer_types() {
142 assert_eq!(pg_type_to_graphql("integer"), GraphQLType::Int);
143 assert_eq!(pg_type_to_graphql("int4"), GraphQLType::Int);
144 assert_eq!(pg_type_to_graphql("int"), GraphQLType::Int);
145 assert_eq!(pg_type_to_graphql("smallint"), GraphQLType::Int);
146 assert_eq!(pg_type_to_graphql("int2"), GraphQLType::Int);
147 }
148
149 #[test]
150 fn test_pg_to_graphql_bigint() {
151 assert_eq!(pg_type_to_graphql("bigint"), GraphQLType::BigInt);
152 assert_eq!(pg_type_to_graphql("int8"), GraphQLType::BigInt);
153 }
154
155 #[test]
156 fn test_pg_to_graphql_float_types() {
157 assert_eq!(pg_type_to_graphql("real"), GraphQLType::Float);
158 assert_eq!(pg_type_to_graphql("float4"), GraphQLType::Float);
159 assert_eq!(pg_type_to_graphql("double precision"), GraphQLType::Float);
160 assert_eq!(pg_type_to_graphql("float8"), GraphQLType::Float);
161 }
162
163 #[test]
164 fn test_pg_to_graphql_numeric_types() {
165 assert_eq!(pg_type_to_graphql("numeric"), GraphQLType::BigDecimal);
166 assert_eq!(pg_type_to_graphql("decimal"), GraphQLType::BigDecimal);
167 }
168
169 #[test]
170 fn test_pg_to_graphql_string_types() {
171 assert_eq!(pg_type_to_graphql("text"), GraphQLType::String);
172 assert_eq!(pg_type_to_graphql("varchar"), GraphQLType::String);
173 assert_eq!(pg_type_to_graphql("character varying"), GraphQLType::String);
174 assert_eq!(pg_type_to_graphql("char"), GraphQLType::String);
175 assert_eq!(pg_type_to_graphql("bpchar"), GraphQLType::String);
176 }
177
178 #[test]
179 fn test_pg_to_graphql_boolean() {
180 assert_eq!(pg_type_to_graphql("boolean"), GraphQLType::Boolean);
181 assert_eq!(pg_type_to_graphql("bool"), GraphQLType::Boolean);
182 }
183
184 #[test]
185 fn test_pg_to_graphql_json() {
186 assert_eq!(pg_type_to_graphql("jsonb"), GraphQLType::Json);
188 assert_eq!(
189 pg_type_to_graphql("json"),
190 GraphQLType::Custom("json".to_string())
191 );
192 }
193
194 #[test]
195 fn test_pg_to_graphql_uuid() {
196 assert_eq!(pg_type_to_graphql("uuid"), GraphQLType::Uuid);
197 }
198
199 #[test]
200 fn test_pg_to_graphql_datetime_types() {
201 assert_eq!(
202 pg_type_to_graphql("timestamp"),
203 GraphQLType::Custom("timestamp".to_string())
204 );
205 assert_eq!(pg_type_to_graphql("timestamptz"), GraphQLType::DateTime);
206 assert_eq!(
207 pg_type_to_graphql("timestamp with time zone"),
208 GraphQLType::DateTime
209 );
210 assert_eq!(
211 pg_type_to_graphql("timestamp without time zone"),
212 GraphQLType::Custom("timestamp".to_string())
213 );
214 }
215
216 #[test]
217 fn test_pg_to_graphql_date() {
218 assert_eq!(pg_type_to_graphql("date"), GraphQLType::Date);
219 }
220
221 #[test]
222 fn test_pg_to_graphql_time() {
223 assert_eq!(pg_type_to_graphql("time"), GraphQLType::Time);
224 assert_eq!(
225 pg_type_to_graphql("timetz"),
226 GraphQLType::Custom("timetz".to_string())
227 );
228 assert_eq!(
229 pg_type_to_graphql("time with time zone"),
230 GraphQLType::Custom("timetz".to_string())
231 );
232 }
233
234 #[test]
235 fn test_pg_to_graphql_array_types_underscore() {
236 assert_eq!(
237 pg_type_to_graphql("_int4"),
238 GraphQLType::List(Box::new(GraphQLType::Int))
239 );
240 assert_eq!(
241 pg_type_to_graphql("_text"),
242 GraphQLType::List(Box::new(GraphQLType::String))
243 );
244 assert_eq!(
245 pg_type_to_graphql("_uuid"),
246 GraphQLType::List(Box::new(GraphQLType::Uuid))
247 );
248 }
249
250 #[test]
251 fn test_pg_to_graphql_array_types_bracket() {
252 assert_eq!(
253 pg_type_to_graphql("integer[]"),
254 GraphQLType::List(Box::new(GraphQLType::Int))
255 );
256 assert_eq!(
257 pg_type_to_graphql("text[]"),
258 GraphQLType::List(Box::new(GraphQLType::String))
259 );
260 }
261
262 #[test]
263 fn test_pg_to_graphql_unknown_keeps_its_own_name() {
264 assert_eq!(
267 pg_type_to_graphql("customtype"),
268 GraphQLType::Custom("customtype".to_string())
269 );
270 assert_eq!(
271 pg_type_to_graphql("my_domain"),
272 GraphQLType::Custom("my_domain".to_string())
273 );
274 }
275
276 #[test]
277 fn test_pg_to_graphql_case_insensitive() {
278 assert_eq!(pg_type_to_graphql("INTEGER"), GraphQLType::Int);
279 assert_eq!(pg_type_to_graphql("Text"), GraphQLType::String);
280 assert_eq!(pg_type_to_graphql("BOOLEAN"), GraphQLType::Boolean);
281 }
282
283 #[test]
284 fn test_graphql_type_display() {
285 assert_eq!(format!("{}", GraphQLType::Int), "Int");
286 assert_eq!(format!("{}", GraphQLType::String), "String");
287 assert_eq!(
288 format!("{}", GraphQLType::List(Box::new(GraphQLType::Int))),
289 "[Int]"
290 );
291 }
292
293 #[test]
294 fn test_is_nullable_type() {
295 assert!(!is_nullable_type(true, true));
297 assert!(!is_nullable_type(false, true));
298
299 assert!(is_nullable_type(true, false));
301 assert!(!is_nullable_type(false, false));
302 }
303}