1use crate::types::{pg_type_to_graphql, GraphQLType};
6use postrust_core::schema_cache::{Column, Table};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone)]
12pub struct InsertField {
13 pub name: String,
15 pub graphql_type: GraphQLType,
17 pub required: bool,
19 pub description: Option<String>,
21}
22
23impl InsertField {
24 pub fn from_column(column: &Column) -> Self {
26 let graphql_type = pg_type_to_graphql(&column.nominal_type);
27
28 let has_auto_default = column
33 .default
34 .as_ref()
35 .is_some_and(|d| d.contains("nextval") || d.contains("gen_random_uuid"));
36
37 let required = !column.nullable && column.default.is_none() && !has_auto_default;
38
39 Self {
40 name: column.name.clone(),
41 description: column.description.clone(),
42 graphql_type,
43 required,
44 }
45 }
46
47 pub fn type_string(&self) -> String {
49 let base = format!("{}", self.graphql_type);
50 if self.required {
51 format!("{}!", base)
52 } else {
53 base
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
60pub struct UpdateField {
61 pub name: String,
63 pub graphql_type: GraphQLType,
65 pub description: Option<String>,
67 pub is_pk: bool,
69}
70
71impl UpdateField {
72 pub fn from_column(column: &Column) -> Self {
74 let graphql_type = pg_type_to_graphql(&column.nominal_type);
75
76 Self {
77 name: column.name.clone(),
78 description: column.description.clone(),
79 graphql_type,
80 is_pk: column.is_pk,
81 }
82 }
83
84 pub fn type_string(&self) -> String {
86 format!("{}", self.graphql_type)
87 }
88
89 pub fn is_updatable(&self) -> bool {
91 !self.is_pk
92 }
93}
94
95#[derive(Debug, Clone)]
97pub struct InsertInput {
98 pub table_name: String,
100 pub type_name: String,
102 pub fields: Vec<InsertField>,
104}
105
106impl InsertInput {
107 pub fn from_table(table: &Table) -> Self {
109 let type_name = format!("{}_insert_input", table.name);
110
111 let fields = table
112 .columns
113 .values()
114 .map(InsertField::from_column)
115 .collect();
116
117 Self {
118 table_name: table.name.clone(),
119 type_name,
120 fields,
121 }
122 }
123
124 pub fn required_fields(&self) -> Vec<&InsertField> {
126 self.fields.iter().filter(|f| f.required).collect()
127 }
128
129 pub fn optional_fields(&self) -> Vec<&InsertField> {
131 self.fields.iter().filter(|f| !f.required).collect()
132 }
133
134 pub fn has_required_fields(&self) -> bool {
136 self.fields.iter().any(|f| f.required)
137 }
138}
139
140#[derive(Debug, Clone)]
142pub struct UpdateInput {
143 pub table_name: String,
145 pub type_name: String,
147 pub fields: Vec<UpdateField>,
149}
150
151impl UpdateInput {
152 pub fn from_table(table: &Table) -> Self {
154 let type_name = format!("{}_set_input", table.name);
155
156 let fields = table
157 .columns
158 .values()
159 .filter(|c| !c.is_pk) .map(UpdateField::from_column)
161 .collect();
162
163 Self {
164 table_name: table.name.clone(),
165 type_name,
166 fields,
167 }
168 }
169
170 pub fn updatable_fields(&self) -> Vec<&UpdateField> {
172 self.fields.iter().filter(|f| f.is_updatable()).collect()
173 }
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178#[serde(untagged)]
179pub enum InputValue {
180 Null,
182 Bool(bool),
184 Int(i64),
186 Float(f64),
188 String(String),
190 Object(HashMap<String, InputValue>),
192 Array(Vec<InputValue>),
194}
195
196impl InputValue {
197 pub fn is_null(&self) -> bool {
199 matches!(self, Self::Null)
200 }
201
202 pub fn as_string(&self) -> Option<&str> {
204 match self {
205 Self::String(s) => Some(s),
206 _ => None,
207 }
208 }
209
210 pub fn as_int(&self) -> Option<i64> {
212 match self {
213 Self::Int(i) => Some(*i),
214 _ => None,
215 }
216 }
217
218 pub fn as_float(&self) -> Option<f64> {
220 match self {
221 Self::Float(f) => Some(*f),
222 Self::Int(i) => Some(*i as f64),
223 _ => None,
224 }
225 }
226
227 pub fn as_bool(&self) -> Option<bool> {
229 match self {
230 Self::Bool(b) => Some(*b),
231 _ => None,
232 }
233 }
234
235 pub fn to_sql_string(&self) -> String {
237 match self {
238 Self::Null => "NULL".to_string(),
239 Self::Bool(b) => if *b { "true" } else { "false" }.to_string(),
240 Self::Int(i) => i.to_string(),
241 Self::Float(f) => f.to_string(),
242 Self::String(s) => s.clone(),
243 Self::Object(o) => serde_json::to_string(o).unwrap_or_default(),
244 Self::Array(a) => serde_json::to_string(a).unwrap_or_default(),
245 }
246 }
247}
248
249pub fn is_insertable(table: &Table) -> bool {
251 table.insertable
252}
253
254pub fn is_updatable(table: &Table) -> bool {
256 table.updatable
257}
258
259pub fn is_deletable(table: &Table) -> bool {
261 table.deletable
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267 use indexmap::IndexMap;
268 use pretty_assertions::assert_eq;
269
270 fn create_test_table() -> Table {
271 let mut columns = IndexMap::new();
272 columns.insert(
273 "id".into(),
274 Column {
275 name: "id".into(),
276 description: Some("Primary key".into()),
277 nullable: false,
278 data_type: "integer".into(),
279 nominal_type: "int4".into(),
280 max_len: None,
281 default: Some("nextval('users_id_seq')".into()),
282 enum_values: vec![],
283 is_pk: true,
284 position: 1,
285 always_generated: false,
286 domain_type: None,
287 },
288 );
289 columns.insert(
290 "name".into(),
291 Column {
292 name: "name".into(),
293 description: Some("User name".into()),
294 nullable: false,
295 data_type: "text".into(),
296 nominal_type: "text".into(),
297 max_len: None,
298 default: None,
299 enum_values: vec![],
300 is_pk: false,
301 position: 2,
302 always_generated: false,
303 domain_type: None,
304 },
305 );
306 columns.insert(
307 "email".into(),
308 Column {
309 name: "email".into(),
310 description: None,
311 nullable: true,
312 data_type: "text".into(),
313 nominal_type: "text".into(),
314 max_len: None,
315 default: None,
316 enum_values: vec![],
317 is_pk: false,
318 position: 3,
319 always_generated: false,
320 domain_type: None,
321 },
322 );
323 columns.insert(
324 "created_at".into(),
325 Column {
326 name: "created_at".into(),
327 description: None,
328 nullable: false,
329 data_type: "timestamptz".into(),
330 nominal_type: "timestamptz".into(),
331 max_len: None,
332 default: Some("now()".into()),
333 enum_values: vec![],
334 is_pk: false,
335 position: 4,
336 always_generated: false,
337 domain_type: None,
338 },
339 );
340
341 Table {
342 schema: "public".into(),
343 name: "users".into(),
344 description: Some("User accounts".into()),
345 is_view: false,
346 insertable: true,
347 updatable: true,
348 deletable: true,
349 pk_cols: vec!["id".into()],
350 unique_constraints: Vec::new(),
351 columns,
352 computed_columns: Default::default(),
353 is_partitioned: false,
354 }
355 }
356
357 fn create_readonly_table() -> Table {
358 let mut table = create_test_table();
359 table.insertable = false;
360 table.updatable = false;
361 table.deletable = false;
362 table
363 }
364
365 #[test]
370 fn test_insert_field_required() {
371 let table = create_test_table();
372 let name_col = table.columns.get("name").unwrap();
373 let field = InsertField::from_column(name_col);
374
375 assert_eq!(field.name, "name");
376 assert!(field.required); assert_eq!(field.type_string(), "String!");
378 }
379
380 #[test]
381 fn test_insert_field_optional_nullable() {
382 let table = create_test_table();
383 let email_col = table.columns.get("email").unwrap();
384 let field = InsertField::from_column(email_col);
385
386 assert_eq!(field.name, "email");
387 assert!(!field.required); assert_eq!(field.type_string(), "String");
389 }
390
391 #[test]
392 fn test_insert_field_optional_with_default() {
393 let table = create_test_table();
394 let created_at_col = table.columns.get("created_at").unwrap();
395 let field = InsertField::from_column(created_at_col);
396
397 assert_eq!(field.name, "created_at");
398 assert!(!field.required); assert_eq!(field.type_string(), "timestamptz");
400 }
401
402 #[test]
403 fn test_insert_field_auto_pk() {
404 let table = create_test_table();
405 let id_col = table.columns.get("id").unwrap();
406 let field = InsertField::from_column(id_col);
407
408 assert_eq!(field.name, "id");
409 assert!(!field.required); }
411
412 #[test]
417 fn test_update_field_non_pk() {
418 let table = create_test_table();
419 let name_col = table.columns.get("name").unwrap();
420 let field = UpdateField::from_column(name_col);
421
422 assert_eq!(field.name, "name");
423 assert!(!field.is_pk);
424 assert!(field.is_updatable());
425 assert_eq!(field.type_string(), "String"); }
427
428 #[test]
429 fn test_update_field_pk() {
430 let table = create_test_table();
431 let id_col = table.columns.get("id").unwrap();
432 let field = UpdateField::from_column(id_col);
433
434 assert_eq!(field.name, "id");
435 assert!(field.is_pk);
436 assert!(!field.is_updatable());
437 }
438
439 #[test]
444 fn test_insert_input_from_table() {
445 let table = create_test_table();
446 let input = InsertInput::from_table(&table);
447
448 assert_eq!(input.table_name, "users");
449 assert_eq!(input.type_name, "users_insert_input");
450 assert_eq!(input.fields.len(), 4);
451 }
452
453 #[test]
454 fn test_insert_input_required_fields() {
455 let table = create_test_table();
456 let input = InsertInput::from_table(&table);
457
458 let required = input.required_fields();
459 assert_eq!(required.len(), 1); assert_eq!(required[0].name, "name");
461 }
462
463 #[test]
464 fn test_insert_input_optional_fields() {
465 let table = create_test_table();
466 let input = InsertInput::from_table(&table);
467
468 let optional = input.optional_fields();
469 assert_eq!(optional.len(), 3); }
471
472 #[test]
473 fn test_insert_input_has_required_fields() {
474 let table = create_test_table();
475 let input = InsertInput::from_table(&table);
476
477 assert!(input.has_required_fields());
478 }
479
480 #[test]
485 fn test_update_input_from_table() {
486 let table = create_test_table();
487 let input = UpdateInput::from_table(&table);
488
489 assert_eq!(input.table_name, "users");
490 assert_eq!(input.type_name, "users_set_input");
491 assert_eq!(input.fields.len(), 3); }
493
494 #[test]
495 fn test_update_input_excludes_pk() {
496 let table = create_test_table();
497 let input = UpdateInput::from_table(&table);
498
499 let field_names: Vec<_> = input.fields.iter().map(|f| f.name.as_str()).collect();
500 assert!(!field_names.contains(&"id"));
501 }
502
503 #[test]
504 fn test_update_input_updatable_fields() {
505 let table = create_test_table();
506 let input = UpdateInput::from_table(&table);
507
508 let updatable = input.updatable_fields();
509 assert_eq!(updatable.len(), 3);
510 }
511
512 #[test]
517 fn test_input_value_null() {
518 let value = InputValue::Null;
519 assert!(value.is_null());
520 assert_eq!(value.to_sql_string(), "NULL");
521 }
522
523 #[test]
524 fn test_input_value_bool() {
525 let value = InputValue::Bool(true);
526 assert_eq!(value.as_bool(), Some(true));
527 assert_eq!(value.to_sql_string(), "true");
528
529 let value = InputValue::Bool(false);
530 assert_eq!(value.to_sql_string(), "false");
531 }
532
533 #[test]
534 fn test_input_value_int() {
535 let value = InputValue::Int(42);
536 assert_eq!(value.as_int(), Some(42));
537 assert_eq!(value.as_float(), Some(42.0)); assert_eq!(value.to_sql_string(), "42");
539 }
540
541 #[test]
542 fn test_input_value_float() {
543 let value = InputValue::Float(1.5);
544 assert_eq!(value.as_float(), Some(1.5));
545 assert_eq!(value.to_sql_string(), "1.5");
546 }
547
548 #[test]
549 fn test_input_value_string() {
550 let value = InputValue::String("hello".to_string());
551 assert_eq!(value.as_string(), Some("hello"));
552 assert_eq!(value.to_sql_string(), "hello");
553 }
554
555 #[test]
560 fn test_is_insertable() {
561 let table = create_test_table();
562 assert!(is_insertable(&table));
563
564 let readonly = create_readonly_table();
565 assert!(!is_insertable(&readonly));
566 }
567
568 #[test]
569 fn test_is_updatable() {
570 let table = create_test_table();
571 assert!(is_updatable(&table));
572
573 let readonly = create_readonly_table();
574 assert!(!is_updatable(&readonly));
575 }
576
577 #[test]
578 fn test_is_deletable() {
579 let table = create_test_table();
580 assert!(is_deletable(&table));
581
582 let readonly = create_readonly_table();
583 assert!(!is_deletable(&readonly));
584 }
585
586 }