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"),
47 GraphQLType::BigDecimal => write!(f, "BigDecimal"),
48 GraphQLType::Json => write!(f, "JSON"),
49 GraphQLType::Uuid => write!(f, "UUID"),
50 GraphQLType::Date => write!(f, "Date"),
51 GraphQLType::DateTime => write!(f, "DateTime"),
52 GraphQLType::Time => write!(f, "Time"),
53 GraphQLType::List(inner) => write!(f, "[{}]", inner),
54 GraphQLType::Custom(name) => write!(f, "{}", name),
55 }
56 }
57}
58
59pub fn pg_type_to_graphql(pg_type: &str) -> GraphQLType {
61 let normalized = pg_type.to_lowercase().trim().to_string();
63
64 if let Some(inner_type) = normalized.strip_prefix('_') {
66 return GraphQLType::List(Box::new(pg_type_to_graphql(inner_type)));
68 }
69
70 if normalized.ends_with("[]") {
71 let inner_type = normalized.trim_end_matches("[]");
73 return GraphQLType::List(Box::new(pg_type_to_graphql(inner_type)));
74 }
75
76 match normalized.as_str() {
77 "integer" | "int" | "int4" | "smallint" | "int2" => GraphQLType::Int,
79
80 "bigint" | "int8" => GraphQLType::BigInt,
82
83 "real" | "float4" | "double precision" | "float8" => GraphQLType::Float,
85
86 "numeric" | "decimal" => GraphQLType::BigDecimal,
88
89 "boolean" | "bool" => GraphQLType::Boolean,
91
92 "text" | "varchar" | "character varying" | "char" | "character" | "bpchar" => {
94 GraphQLType::String
95 }
96
97 "json" | "jsonb" => GraphQLType::Json,
99
100 "uuid" => GraphQLType::Uuid,
102
103 "timestamp"
105 | "timestamp without time zone"
106 | "timestamptz"
107 | "timestamp with time zone" => GraphQLType::DateTime,
108 "date" => GraphQLType::Date,
109 "time" | "time without time zone" | "timetz" | "time with time zone" => GraphQLType::Time,
110
111 _ => GraphQLType::String,
113 }
114}
115
116pub fn is_nullable_type(nullable: bool, is_pk: bool) -> bool {
118 if is_pk {
120 return false;
121 }
122 nullable
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use pretty_assertions::assert_eq;
129
130 #[test]
131 fn test_pg_to_graphql_integer_types() {
132 assert_eq!(pg_type_to_graphql("integer"), GraphQLType::Int);
133 assert_eq!(pg_type_to_graphql("int4"), GraphQLType::Int);
134 assert_eq!(pg_type_to_graphql("int"), GraphQLType::Int);
135 assert_eq!(pg_type_to_graphql("smallint"), GraphQLType::Int);
136 assert_eq!(pg_type_to_graphql("int2"), GraphQLType::Int);
137 }
138
139 #[test]
140 fn test_pg_to_graphql_bigint() {
141 assert_eq!(pg_type_to_graphql("bigint"), GraphQLType::BigInt);
142 assert_eq!(pg_type_to_graphql("int8"), GraphQLType::BigInt);
143 }
144
145 #[test]
146 fn test_pg_to_graphql_float_types() {
147 assert_eq!(pg_type_to_graphql("real"), GraphQLType::Float);
148 assert_eq!(pg_type_to_graphql("float4"), GraphQLType::Float);
149 assert_eq!(pg_type_to_graphql("double precision"), GraphQLType::Float);
150 assert_eq!(pg_type_to_graphql("float8"), GraphQLType::Float);
151 }
152
153 #[test]
154 fn test_pg_to_graphql_numeric_types() {
155 assert_eq!(pg_type_to_graphql("numeric"), GraphQLType::BigDecimal);
156 assert_eq!(pg_type_to_graphql("decimal"), GraphQLType::BigDecimal);
157 }
158
159 #[test]
160 fn test_pg_to_graphql_string_types() {
161 assert_eq!(pg_type_to_graphql("text"), GraphQLType::String);
162 assert_eq!(pg_type_to_graphql("varchar"), GraphQLType::String);
163 assert_eq!(pg_type_to_graphql("character varying"), GraphQLType::String);
164 assert_eq!(pg_type_to_graphql("char"), GraphQLType::String);
165 assert_eq!(pg_type_to_graphql("bpchar"), GraphQLType::String);
166 }
167
168 #[test]
169 fn test_pg_to_graphql_boolean() {
170 assert_eq!(pg_type_to_graphql("boolean"), GraphQLType::Boolean);
171 assert_eq!(pg_type_to_graphql("bool"), GraphQLType::Boolean);
172 }
173
174 #[test]
175 fn test_pg_to_graphql_json() {
176 assert_eq!(pg_type_to_graphql("json"), GraphQLType::Json);
177 assert_eq!(pg_type_to_graphql("jsonb"), GraphQLType::Json);
178 }
179
180 #[test]
181 fn test_pg_to_graphql_uuid() {
182 assert_eq!(pg_type_to_graphql("uuid"), GraphQLType::Uuid);
183 }
184
185 #[test]
186 fn test_pg_to_graphql_datetime_types() {
187 assert_eq!(pg_type_to_graphql("timestamp"), GraphQLType::DateTime);
188 assert_eq!(pg_type_to_graphql("timestamptz"), GraphQLType::DateTime);
189 assert_eq!(
190 pg_type_to_graphql("timestamp with time zone"),
191 GraphQLType::DateTime
192 );
193 assert_eq!(
194 pg_type_to_graphql("timestamp without time zone"),
195 GraphQLType::DateTime
196 );
197 }
198
199 #[test]
200 fn test_pg_to_graphql_date() {
201 assert_eq!(pg_type_to_graphql("date"), GraphQLType::Date);
202 }
203
204 #[test]
205 fn test_pg_to_graphql_time() {
206 assert_eq!(pg_type_to_graphql("time"), GraphQLType::Time);
207 assert_eq!(pg_type_to_graphql("timetz"), GraphQLType::Time);
208 assert_eq!(pg_type_to_graphql("time with time zone"), GraphQLType::Time);
209 }
210
211 #[test]
212 fn test_pg_to_graphql_array_types_underscore() {
213 assert_eq!(
214 pg_type_to_graphql("_int4"),
215 GraphQLType::List(Box::new(GraphQLType::Int))
216 );
217 assert_eq!(
218 pg_type_to_graphql("_text"),
219 GraphQLType::List(Box::new(GraphQLType::String))
220 );
221 assert_eq!(
222 pg_type_to_graphql("_uuid"),
223 GraphQLType::List(Box::new(GraphQLType::Uuid))
224 );
225 }
226
227 #[test]
228 fn test_pg_to_graphql_array_types_bracket() {
229 assert_eq!(
230 pg_type_to_graphql("integer[]"),
231 GraphQLType::List(Box::new(GraphQLType::Int))
232 );
233 assert_eq!(
234 pg_type_to_graphql("text[]"),
235 GraphQLType::List(Box::new(GraphQLType::String))
236 );
237 }
238
239 #[test]
240 fn test_pg_to_graphql_unknown_defaults_to_string() {
241 assert_eq!(pg_type_to_graphql("customtype"), GraphQLType::String);
242 assert_eq!(pg_type_to_graphql("my_domain"), GraphQLType::String);
243 }
244
245 #[test]
246 fn test_pg_to_graphql_case_insensitive() {
247 assert_eq!(pg_type_to_graphql("INTEGER"), GraphQLType::Int);
248 assert_eq!(pg_type_to_graphql("Text"), GraphQLType::String);
249 assert_eq!(pg_type_to_graphql("BOOLEAN"), GraphQLType::Boolean);
250 }
251
252 #[test]
253 fn test_graphql_type_display() {
254 assert_eq!(format!("{}", GraphQLType::Int), "Int");
255 assert_eq!(format!("{}", GraphQLType::String), "String");
256 assert_eq!(
257 format!("{}", GraphQLType::List(Box::new(GraphQLType::Int))),
258 "[Int]"
259 );
260 }
261
262 #[test]
263 fn test_is_nullable_type() {
264 assert!(!is_nullable_type(true, true));
266 assert!(!is_nullable_type(false, true));
267
268 assert!(is_nullable_type(true, false));
270 assert!(!is_nullable_type(false, false));
271 }
272}