1use crate::context::GraphQLContext;
7use crate::error::GraphQLError;
8use crate::schema::object::TableObjectType;
9use crate::schema::{build_schema, GeneratedSchema, MutationType, SchemaConfig};
10use crate::subscription::{
11 generate_subscription_fields, NotifyBroker, SubscriptionField as SubField, TableChangePayload,
12};
13use async_graphql::dynamic::*;
14use async_graphql::Value;
15use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
16use axum::extract::State;
17use axum::response::IntoResponse;
18use futures::stream::StreamExt;
19use postrust_core::schema_cache::SchemaCache;
20use sqlx::PgPool;
21use std::collections::HashMap;
22use std::sync::Arc;
23use tokio::sync::RwLock;
24use tracing::{debug, info};
25
26pub struct GraphQLState {
28 pub pool: PgPool,
30 pub schema_cache: Arc<SchemaCache>,
32 pub generated_schema: GeneratedSchema,
34 pub schema: Schema,
36 pub config: SchemaConfig,
38 pub subscription_fields: Vec<SubField>,
40 pub broker: Arc<RwLock<Option<NotifyBroker>>>,
42}
43
44impl GraphQLState {
45 pub fn new(
47 pool: PgPool,
48 schema_cache: Arc<SchemaCache>,
49 config: SchemaConfig,
50 ) -> Result<Self, GraphQLError> {
51 let generated_schema = build_schema(&schema_cache, &config);
52 let subscription_fields = if config.enable_subscriptions {
53 generate_subscription_fields(&schema_cache, &generated_schema)
54 } else {
55 Vec::new()
56 };
57 let schema = build_dynamic_schema(
58 &generated_schema,
59 &schema_cache,
60 if config.enable_subscriptions {
61 Some(subscription_fields.as_slice())
62 } else {
63 None
64 },
65 )?;
66
67 Ok(Self {
68 pool: pool.clone(),
69 schema_cache,
70 generated_schema,
71 schema,
72 config,
73 subscription_fields,
74 broker: Arc::new(RwLock::new(None)),
75 })
76 }
77
78 pub fn rebuild(&mut self) -> Result<(), GraphQLError> {
80 self.generated_schema = build_schema(&self.schema_cache, &self.config);
81 self.subscription_fields = if self.config.enable_subscriptions {
82 generate_subscription_fields(&self.schema_cache, &self.generated_schema)
83 } else {
84 Vec::new()
85 };
86 self.schema = build_dynamic_schema(
87 &self.generated_schema,
88 &self.schema_cache,
89 if self.config.enable_subscriptions {
90 Some(self.subscription_fields.as_slice())
91 } else {
92 None
93 },
94 )?;
95 Ok(())
96 }
97
98 pub async fn init_subscriptions(&self) -> Result<(), crate::subscription::BrokerError> {
102 if !self.config.enable_subscriptions {
103 return Ok(());
104 }
105
106 let broker = NotifyBroker::new(self.pool.clone());
107
108 let channels: Vec<String> = self
110 .subscription_fields
111 .iter()
112 .map(|f| f.channel_name())
113 .collect();
114
115 if !channels.is_empty() {
116 broker.start(channels).await?;
117 info!(
118 "Subscription broker started with {} channels",
119 self.subscription_fields.len()
120 );
121 }
122
123 let mut broker_guard = self.broker.write().await;
125 *broker_guard = Some(broker);
126
127 Ok(())
128 }
129
130 pub async fn stop_subscriptions(&self) {
132 let broker_guard = self.broker.read().await;
133 if let Some(broker) = broker_guard.as_ref() {
134 broker.stop().await;
135 }
136 }
137
138 pub async fn get_broker(&self) -> Option<Arc<RwLock<Option<NotifyBroker>>>> {
140 Some(Arc::clone(&self.broker))
141 }
142}
143
144pub async fn graphql_handler(
146 State(state): State<Arc<GraphQLState>>,
147 ctx: GraphQLContext,
148 req: GraphQLRequest,
149) -> GraphQLResponse {
150 let request = req
151 .into_inner()
152 .data(ctx)
153 .data(state.pool.clone())
154 .data(Arc::clone(&state.broker));
155 state.schema.execute(request).await.into()
156}
157
158pub async fn graphql_ws_handler(
163 State(state): State<Arc<GraphQLState>>,
164 protocol: async_graphql_axum::GraphQLProtocol,
165 ws: axum::extract::WebSocketUpgrade,
166) -> impl IntoResponse {
167 let schema = state.schema.clone();
168 let pool = state.pool.clone();
169 let broker = Arc::clone(&state.broker);
170
171 ws.protocols(["graphql-transport-ws", "graphql-ws"])
172 .on_upgrade(move |socket| async move {
173 let mut data = async_graphql::Data::default();
174 data.insert(pool);
175 data.insert(broker);
176
177 async_graphql_axum::GraphQLWebSocket::new(socket, schema, protocol)
178 .with_data(data)
179 .serve()
180 .await
181 })
182}
183
184pub async fn graphql_playground() -> impl axum::response::IntoResponse {
186 axum::response::Html(async_graphql::http::playground_source(
187 async_graphql::http::GraphQLPlaygroundConfig::new("/graphql")
188 .subscription_endpoint("/graphql/ws"),
189 ))
190}
191
192fn build_dynamic_schema(
194 generated: &GeneratedSchema,
195 _schema_cache: &SchemaCache,
196 subscription_fields: Option<&[SubField]>,
197) -> Result<Schema, GraphQLError> {
198 let mut object_types: HashMap<String, Object> = HashMap::new();
200
201 for (type_name, obj) in &generated.object_types {
202 let table_obj = create_object_type(obj);
203 object_types.insert(type_name.clone(), table_obj);
204 }
205
206 let query = create_query_type(generated);
208
209 let mutation = if !generated.mutation_fields.is_empty() {
211 Some(create_mutation_type(generated))
212 } else {
213 None
214 };
215
216 let subscription = subscription_fields.map(create_subscription_type);
218
219 let mut builder = Schema::build(
221 "Query",
222 mutation.as_ref().map(|_| "Mutation"),
223 subscription.as_ref().map(|_| "Subscription"),
224 );
225
226 for (_, obj) in object_types {
228 builder = builder.register(obj);
229 }
230
231 builder = builder.register(query);
233
234 if let Some(mutation) = mutation {
236 builder = builder.register(mutation);
237 }
238
239 if let Some(subscription) = subscription {
241 builder = builder.register(subscription);
242 }
243
244 builder = builder.register(create_bigint_scalar());
246 builder = builder.register(create_bigdecimal_scalar());
247 builder = builder.register(create_json_scalar());
248 builder = builder.register(create_uuid_scalar());
249 builder = builder.register(create_date_scalar());
250 builder = builder.register(create_datetime_scalar());
251 builder = builder.register(create_time_scalar());
252
253 builder = register_filter_input_types(builder);
255
256 builder
257 .finish()
258 .map_err(|e| GraphQLError::SchemaError(e.to_string()))
259}
260
261fn create_object_type(obj: &TableObjectType) -> Object {
263 let mut object = Object::new(&obj.name);
264
265 if let Some(desc) = obj.description() {
266 object = object.description(desc);
267 }
268
269 for field in &obj.fields {
270 let field_type = graphql_type_ref(&field.type_string());
271 let mut gql_field = Field::new(&field.name, field_type, |_| {
272 FieldFuture::new(async move { Ok(None::<FieldValue>) })
273 });
274
275 if let Some(desc) = &field.description {
276 gql_field = gql_field.description(desc);
277 }
278
279 object = object.field(gql_field);
280 }
281
282 object
283}
284
285fn create_query_type(generated: &GeneratedSchema) -> Object {
287 let mut query = Object::new("Query");
288
289 for field in &generated.query_fields {
290 let table_name = field.table_name.clone();
291 let is_by_pk = field.is_by_pk;
292 let return_type = graphql_type_ref(&field.return_type);
293
294 let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
295 let table_name = table_name.clone();
296 FieldFuture::new(async move {
297 resolve_query(&ctx, &table_name, is_by_pk).await
298 })
299 });
300
301 if !is_by_pk {
303 gql_field = gql_field
304 .argument(InputValue::new("filter", TypeRef::named("JSON")))
305 .argument(InputValue::new("orderBy", TypeRef::named_list("String")))
306 .argument(InputValue::new("limit", TypeRef::named("Int")))
307 .argument(InputValue::new("offset", TypeRef::named("Int")));
308 } else {
309 gql_field = gql_field.argument(InputValue::new("id", TypeRef::named_nn("Int")));
311 }
312
313 if let Some(desc) = &field.description {
314 gql_field = gql_field.description(desc);
315 }
316
317 query = query.field(gql_field);
318 }
319
320 query = query.field(
322 Field::new("_schema", TypeRef::named("String"), |_| {
323 FieldFuture::new(async move {
324 Ok(Some(Value::String("Postrust GraphQL Schema".to_string())))
325 })
326 })
327 .description("Schema introspection"),
328 );
329
330 query
331}
332
333fn create_mutation_type(generated: &GeneratedSchema) -> Object {
335 let mut mutation = Object::new("Mutation");
336
337 for field in &generated.mutation_fields {
338 let table_name = field.table_name.clone();
339 let mutation_type = field.mutation_type;
340 let return_type = graphql_type_ref(&field.return_type);
341
342 let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
343 let table_name = table_name.clone();
344 FieldFuture::new(async move {
345 resolve_mutation(&ctx, &table_name, mutation_type).await
346 })
347 });
348
349 match mutation_type {
351 MutationType::Insert | MutationType::InsertOne => {
352 gql_field = gql_field
353 .argument(InputValue::new("objects", TypeRef::named_nn_list("JSON")));
354 }
355 MutationType::Update | MutationType::UpdateByPk => {
356 gql_field = gql_field
357 .argument(InputValue::new("where", TypeRef::named("JSON")))
358 .argument(InputValue::new("set", TypeRef::named_nn("JSON")));
359 }
360 MutationType::Delete | MutationType::DeleteByPk => {
361 gql_field = gql_field.argument(InputValue::new("where", TypeRef::named("JSON")));
362 }
363 }
364
365 if let Some(desc) = &field.description {
366 gql_field = gql_field.description(desc);
367 }
368
369 mutation = mutation.field(gql_field);
370 }
371
372 mutation
373}
374
375fn create_subscription_type(fields: &[SubField]) -> Subscription {
377 let mut subscription = Subscription::new("Subscription");
378
379 for field in fields {
380 let channel_name = field.channel_name();
381 let return_type = TypeRef::named(&field.return_type);
382 let field_name = field.name.clone();
383 let description = field.description.clone();
384
385 let gql_field = SubscriptionField::new(&field_name, return_type, move |ctx| {
386 let channel_name = channel_name.clone();
387 SubscriptionFieldFuture::new(async move {
388 let broker_arc = ctx.data::<Arc<RwLock<Option<NotifyBroker>>>>()?;
389 let broker_guard = broker_arc.read().await;
390
391 let broker = broker_guard
392 .as_ref()
393 .ok_or_else(|| async_graphql::Error::new("Subscription broker not initialized"))?;
394
395 let stream = broker
396 .subscribe(&channel_name)
397 .await
398 .map_err(|e| async_graphql::Error::new(format!("Subscription error: {}", e)))?;
399
400 let value_stream = stream.filter_map(|notification| async move {
402 match TableChangePayload::from_payload(¬ification.payload) {
403 Ok(payload) => {
404 if let Some(data) = payload.data() {
405 Some(Ok(FieldValue::value(json_to_value(data.clone()))))
406 } else {
407 None
408 }
409 }
410 Err(e) => {
411 debug!("Failed to parse notification payload: {}", e);
412 None
413 }
414 }
415 });
416
417 Ok(value_stream)
418 })
419 });
420
421 let gql_field = if let Some(desc) = description {
422 gql_field.description(desc)
423 } else {
424 gql_field
425 };
426
427 subscription = subscription.field(gql_field);
428 }
429
430 subscription
431}
432
433async fn resolve_query(
435 ctx: &ResolverContext<'_>,
436 table_name: &str,
437 is_by_pk: bool,
438) -> Result<Option<Value>, async_graphql::Error> {
439 let pool = ctx.data::<PgPool>()?;
440 let gql_ctx = ctx.data::<GraphQLContext>()?;
441
442 debug!("Resolving query for table: {}", table_name);
443
444 let limit: Option<i64> = ctx
446 .args
447 .try_get("limit")
448 .ok()
449 .and_then(|v| v.i64().ok());
450
451 let offset: Option<i64> = ctx
452 .args
453 .try_get("offset")
454 .ok()
455 .and_then(|v| v.i64().ok());
456
457 let mut sql = format!(
459 "SELECT row_to_json(t) FROM (SELECT * FROM public.{}) t",
460 table_name
461 );
462
463 if let Some(limit) = limit {
464 sql.push_str(&format!(" LIMIT {}", limit));
465 }
466
467 if let Some(offset) = offset {
468 sql.push_str(&format!(" OFFSET {}", offset));
469 }
470
471 let result = execute_query(pool, &sql, gql_ctx.role()).await?;
473
474 if is_by_pk {
475 Ok(result.first().cloned())
476 } else {
477 Ok(Some(Value::List(result)))
478 }
479}
480
481async fn resolve_mutation(
483 ctx: &ResolverContext<'_>,
484 table_name: &str,
485 mutation_type: MutationType,
486) -> Result<Option<Value>, async_graphql::Error> {
487 let pool = ctx.data::<PgPool>()?;
488 let gql_ctx = ctx.data::<GraphQLContext>()?;
489
490 debug!("Resolving mutation for table: {} type: {:?}", table_name, mutation_type);
491
492 let result = match mutation_type {
493 MutationType::Insert | MutationType::InsertOne => {
494 let objects = ctx
495 .args
496 .try_get("objects")
497 .ok()
498 .map(|v| accessor_to_json(&v))
499 .unwrap_or_else(|| serde_json::Value::Array(vec![]));
500
501 execute_insert(pool, table_name, gql_ctx.role(), objects).await?
502 }
503 MutationType::Update | MutationType::UpdateByPk => {
504 let set_value = ctx
505 .args
506 .try_get("set")
507 .ok()
508 .map(|v| accessor_to_json(&v))
509 .unwrap_or_else(|| serde_json::json!({}));
510
511 execute_update(pool, table_name, gql_ctx.role(), set_value).await?
512 }
513 MutationType::Delete | MutationType::DeleteByPk => {
514 execute_delete(pool, table_name, gql_ctx.role()).await?
515 }
516 };
517
518 Ok(Some(result))
519}
520
521async fn execute_query(
523 pool: &PgPool,
524 sql: &str,
525 role: &str,
526) -> Result<Vec<Value>, async_graphql::Error> {
527 use sqlx::Row;
528
529 debug!("Executing SQL: {}", sql);
530
531 let mut conn = pool.acquire().await?;
532
533 sqlx::query(&format!("SET LOCAL ROLE {}", postrust_sql::escape_ident(role)))
535 .execute(&mut *conn)
536 .await?;
537
538 let rows = sqlx::query(sql).fetch_all(&mut *conn).await?;
540
541 let results: Vec<Value> = rows
543 .iter()
544 .filter_map(|row| {
545 row.try_get::<serde_json::Value, _>(0)
546 .ok()
547 .map(json_to_value)
548 })
549 .collect();
550
551 Ok(results)
552}
553
554async fn execute_insert(
556 _pool: &PgPool,
557 table_name: &str,
558 _role: &str,
559 objects: serde_json::Value,
560) -> Result<Value, async_graphql::Error> {
561 debug!("Insert mutation for {}: {:?}", table_name, objects);
563 Ok(Value::List(vec![]))
564}
565
566async fn execute_update(
568 _pool: &PgPool,
569 table_name: &str,
570 _role: &str,
571 set_value: serde_json::Value,
572) -> Result<Value, async_graphql::Error> {
573 debug!("Update mutation for {}: {:?}", table_name, set_value);
575 Ok(Value::List(vec![]))
576}
577
578async fn execute_delete(
580 _pool: &PgPool,
581 table_name: &str,
582 _role: &str,
583) -> Result<Value, async_graphql::Error> {
584 debug!("Delete mutation for {}", table_name);
586 Ok(Value::List(vec![]))
587}
588
589fn graphql_type_ref(type_str: &str) -> TypeRef {
591 let is_list = type_str.starts_with('[');
593 let is_nn = type_str.ends_with('!');
594
595 let inner = if is_list {
597 let stripped = type_str
598 .trim_end_matches('!') .trim_start_matches('[') .trim_end_matches(']'); stripped
602 } else {
603 type_str.trim_end_matches('!')
604 };
605
606 let inner_nn = inner.ends_with('!');
607 let base_type = inner.trim_end_matches('!');
608
609 if is_list {
610 if is_nn {
611 if inner_nn {
612 TypeRef::named_nn_list_nn(base_type)
613 } else {
614 TypeRef::named_list_nn(base_type)
615 }
616 } else if inner_nn {
617 TypeRef::named_nn_list(base_type)
618 } else {
619 TypeRef::named_list(base_type)
620 }
621 } else if is_nn {
622 TypeRef::named_nn(base_type)
623 } else {
624 TypeRef::named(base_type)
625 }
626}
627
628fn accessor_to_json(accessor: &ValueAccessor<'_>) -> serde_json::Value {
630 if accessor.is_null() {
632 serde_json::Value::Null
633 } else if let Ok(b) = accessor.boolean() {
634 serde_json::Value::Bool(b)
635 } else if let Ok(i) = accessor.i64() {
636 serde_json::Value::Number(i.into())
637 } else if let Ok(f) = accessor.f64() {
638 serde_json::Number::from_f64(f)
639 .map(serde_json::Value::Number)
640 .unwrap_or(serde_json::Value::Null)
641 } else if let Ok(s) = accessor.string() {
642 serde_json::Value::String(s.to_string())
643 } else if let Ok(list) = accessor.list() {
644 serde_json::Value::Array(
645 list.iter()
646 .map(|v| accessor_to_json(&v))
647 .collect()
648 )
649 } else if let Ok(obj) = accessor.object() {
650 let map: serde_json::Map<String, serde_json::Value> = obj
651 .iter()
652 .map(|(k, v)| (k.to_string(), accessor_to_json(&v)))
653 .collect();
654 serde_json::Value::Object(map)
655 } else {
656 serde_json::Value::Null
657 }
658}
659
660fn value_to_json(value: &Value) -> serde_json::Value {
662 match value {
663 Value::Null => serde_json::Value::Null,
664 Value::Boolean(b) => serde_json::Value::Bool(*b),
665 Value::Number(n) => {
666 if let Some(i) = n.as_i64() {
667 serde_json::Value::Number(i.into())
668 } else if let Some(f) = n.as_f64() {
669 serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
670 } else {
671 serde_json::Value::Null
672 }
673 }
674 Value::String(s) => serde_json::Value::String(s.clone()),
675 Value::List(arr) => {
676 serde_json::Value::Array(arr.iter().map(value_to_json).collect())
677 }
678 Value::Object(obj) => {
679 let map: serde_json::Map<String, serde_json::Value> = obj
680 .iter()
681 .map(|(k, v)| (k.to_string(), value_to_json(v)))
682 .collect();
683 serde_json::Value::Object(map)
684 }
685 Value::Binary(b) => serde_json::Value::String(base64::Engine::encode(
686 &base64::engine::general_purpose::STANDARD,
687 b,
688 )),
689 Value::Enum(e) => serde_json::Value::String(e.to_string()),
690 }
691}
692
693fn json_to_value(json: serde_json::Value) -> Value {
695 match json {
696 serde_json::Value::Null => Value::Null,
697 serde_json::Value::Bool(b) => Value::Boolean(b),
698 serde_json::Value::Number(n) => {
699 if let Some(i) = n.as_i64() {
700 Value::Number(i.into())
701 } else if let Some(f) = n.as_f64() {
702 Value::Number(async_graphql::Number::from_f64(f).unwrap())
703 } else {
704 Value::Null
705 }
706 }
707 serde_json::Value::String(s) => Value::String(s),
708 serde_json::Value::Array(arr) => {
709 Value::List(arr.into_iter().map(json_to_value).collect())
710 }
711 serde_json::Value::Object(obj) => {
712 let map: indexmap::IndexMap<async_graphql::Name, Value> = obj
713 .into_iter()
714 .map(|(k, v)| (async_graphql::Name::new(k), json_to_value(v)))
715 .collect();
716 Value::Object(map)
717 }
718 }
719}
720
721fn create_bigint_scalar() -> Scalar {
723 Scalar::new("BigInt")
724 .description("64-bit integer")
725 .specified_by_url("https://spec.graphql.org/draft/#sec-Int")
726}
727
728fn create_bigdecimal_scalar() -> Scalar {
730 Scalar::new("BigDecimal")
731 .description("Arbitrary precision decimal number")
732}
733
734fn create_json_scalar() -> Scalar {
736 Scalar::new("JSON")
737 .description("Arbitrary JSON value")
738 .specified_by_url("https://spec.graphql.org/draft/#sec-Scalars")
739}
740
741fn create_uuid_scalar() -> Scalar {
743 Scalar::new("UUID").description("UUID string")
744}
745
746fn create_date_scalar() -> Scalar {
748 Scalar::new("Date").description("ISO 8601 date string (YYYY-MM-DD)")
749}
750
751fn create_datetime_scalar() -> Scalar {
753 Scalar::new("DateTime").description("ISO 8601 datetime string")
754}
755
756fn create_time_scalar() -> Scalar {
758 Scalar::new("Time").description("ISO 8601 time string (HH:MM:SS)")
759}
760
761fn register_filter_input_types(builder: SchemaBuilder) -> SchemaBuilder {
763 let string_filter = InputObject::new("StringFilterInput")
764 .field(InputValue::new("eq", TypeRef::named("String")))
765 .field(InputValue::new("neq", TypeRef::named("String")))
766 .field(InputValue::new("like", TypeRef::named("String")))
767 .field(InputValue::new("ilike", TypeRef::named("String")))
768 .field(InputValue::new("in", TypeRef::named_list("String")))
769 .field(InputValue::new("isNull", TypeRef::named("Boolean")));
770
771 let int_filter = InputObject::new("IntFilterInput")
772 .field(InputValue::new("eq", TypeRef::named("Int")))
773 .field(InputValue::new("neq", TypeRef::named("Int")))
774 .field(InputValue::new("gt", TypeRef::named("Int")))
775 .field(InputValue::new("gte", TypeRef::named("Int")))
776 .field(InputValue::new("lt", TypeRef::named("Int")))
777 .field(InputValue::new("lte", TypeRef::named("Int")))
778 .field(InputValue::new("in", TypeRef::named_list("Int")));
779
780 let boolean_filter = InputObject::new("BooleanFilterInput")
781 .field(InputValue::new("eq", TypeRef::named("Boolean")));
782
783 builder
784 .register(string_filter)
785 .register(int_filter)
786 .register(boolean_filter)
787}
788
789#[cfg(test)]
790mod tests {
791 use super::*;
792 use indexmap::IndexMap;
793 use postrust_core::schema_cache::{Column, Table};
794 use std::collections::{HashMap, HashSet};
795
796 fn create_test_table(name: &str) -> Table {
797 let mut columns = IndexMap::new();
798 columns.insert(
799 "id".into(),
800 Column {
801 name: "id".into(),
802 description: None,
803 nullable: false,
804 data_type: "integer".into(),
805 nominal_type: "int4".into(),
806 max_len: None,
807 default: Some("nextval('id_seq')".into()),
808 enum_values: vec![],
809 is_pk: true,
810 position: 1,
811 },
812 );
813 columns.insert(
814 "name".into(),
815 Column {
816 name: "name".into(),
817 description: None,
818 nullable: false,
819 data_type: "text".into(),
820 nominal_type: "text".into(),
821 max_len: None,
822 default: None,
823 enum_values: vec![],
824 is_pk: false,
825 position: 2,
826 },
827 );
828
829 Table {
830 schema: "public".into(),
831 name: name.into(),
832 description: None,
833 is_view: false,
834 insertable: true,
835 updatable: true,
836 deletable: true,
837 pk_cols: vec!["id".into()],
838 columns,
839 }
840 }
841
842 fn create_test_schema_cache() -> SchemaCache {
843 let mut tables = HashMap::new();
844 let users = create_test_table("users");
845 tables.insert(users.qualified_identifier(), users);
846
847 SchemaCache {
848 tables,
849 relationships: HashMap::new(),
850 routines: HashMap::new(),
851 timezones: HashSet::new(),
852 pg_version: 150000,
853 }
854 }
855
856 #[test]
861 fn test_graphql_type_ref_simple() {
862 let _type_ref = graphql_type_ref("String");
863 }
865
866 #[test]
867 fn test_graphql_type_ref_non_null() {
868 let _type_ref = graphql_type_ref("String!");
869 }
870
871 #[test]
872 fn test_graphql_type_ref_list() {
873 let _type_ref = graphql_type_ref("[String]");
874 }
875
876 #[test]
877 fn test_graphql_type_ref_list_non_null() {
878 let _type_ref = graphql_type_ref("[String!]!");
879 }
880
881 #[test]
886 fn test_value_to_json_null() {
887 let value = Value::Null;
888 let json = value_to_json(&value);
889 assert_eq!(json, serde_json::Value::Null);
890 }
891
892 #[test]
893 fn test_value_to_json_boolean() {
894 let value = Value::Boolean(true);
895 let json = value_to_json(&value);
896 assert_eq!(json, serde_json::Value::Bool(true));
897 }
898
899 #[test]
900 fn test_value_to_json_number() {
901 let value = Value::Number(42.into());
902 let json = value_to_json(&value);
903 assert_eq!(json, serde_json::json!(42));
904 }
905
906 #[test]
907 fn test_value_to_json_string() {
908 let value = Value::String("hello".to_string());
909 let json = value_to_json(&value);
910 assert_eq!(json, serde_json::Value::String("hello".to_string()));
911 }
912
913 #[test]
914 fn test_value_to_json_list() {
915 let value = Value::List(vec![Value::Number(1.into()), Value::Number(2.into())]);
916 let json = value_to_json(&value);
917 assert_eq!(json, serde_json::json!([1, 2]));
918 }
919
920 #[test]
921 fn test_json_to_value_null() {
922 let json = serde_json::Value::Null;
923 let value = json_to_value(json);
924 assert!(matches!(value, Value::Null));
925 }
926
927 #[test]
928 fn test_json_to_value_boolean() {
929 let json = serde_json::Value::Bool(false);
930 let value = json_to_value(json);
931 assert!(matches!(value, Value::Boolean(false)));
932 }
933
934 #[test]
935 fn test_json_to_value_number() {
936 let json = serde_json::json!(123);
937 let value = json_to_value(json);
938 assert!(matches!(value, Value::Number(_)));
939 }
940
941 #[test]
942 fn test_json_to_value_string() {
943 let json = serde_json::Value::String("test".to_string());
944 let value = json_to_value(json);
945 assert!(matches!(value, Value::String(_)));
946 }
947
948 #[test]
949 fn test_json_to_value_array() {
950 let json = serde_json::json!([1, 2, 3]);
951 let value = json_to_value(json);
952 assert!(matches!(value, Value::List(_)));
953 }
954
955 #[test]
956 fn test_json_to_value_object() {
957 let json = serde_json::json!({"key": "value"});
958 let value = json_to_value(json);
959 assert!(matches!(value, Value::Object(_)));
960 }
961
962 #[test]
967 fn test_build_dynamic_schema() {
968 let cache = create_test_schema_cache();
969 let config = SchemaConfig::default();
970 let generated = build_schema(&cache, &config);
971
972 let result = build_dynamic_schema(&generated, &cache, None);
973 if let Err(ref e) = result {
974 eprintln!("Schema build error: {:?}", e);
975 }
976 assert!(result.is_ok(), "Schema build failed: {:?}", result.err());
977 }
978
979 #[test]
980 fn test_create_object_type() {
981 let table = create_test_table("users");
982 let obj = TableObjectType::from_table(&table);
983 let _gql_obj = create_object_type(&obj);
984 }
985
986 #[test]
987 fn test_create_query_type() {
988 let cache = create_test_schema_cache();
989 let config = SchemaConfig::default();
990 let generated = build_schema(&cache, &config);
991
992 let _query = create_query_type(&generated);
993 }
994
995 #[test]
996 fn test_create_mutation_type() {
997 let cache = create_test_schema_cache();
998 let config = SchemaConfig::default();
999 let generated = build_schema(&cache, &config);
1000
1001 let _mutation = create_mutation_type(&generated);
1002 }
1003
1004 #[test]
1009 fn test_create_scalars() {
1010 let _bigint = create_bigint_scalar();
1011 let _json = create_json_scalar();
1012 let _uuid = create_uuid_scalar();
1013 let _datetime = create_datetime_scalar();
1014 }
1015
1016 #[test]
1021 fn test_register_filter_input_types() {
1022 let cache = create_test_schema_cache();
1023 let config = SchemaConfig::default();
1024 let _generated = build_schema(&cache, &config);
1025
1026 let query = Object::new("Query").field(Field::new(
1028 "test",
1029 TypeRef::named("String"),
1030 |_| FieldFuture::new(async { Ok(None::<FieldValue>) }),
1031 ));
1032
1033 let mut builder = Schema::build("Query", None::<&str>, None);
1034 builder = builder.register(query);
1035 builder = register_filter_input_types(builder);
1036
1037 let result = builder.finish();
1038 assert!(result.is_ok());
1039 }
1040
1041 #[test]
1046 fn test_build_schema_with_subscriptions() {
1047 let cache = create_test_schema_cache();
1048 let config = SchemaConfig {
1049 enable_subscriptions: true,
1050 ..SchemaConfig::default()
1051 };
1052 let generated = build_schema(&cache, &config);
1053
1054 let sub_fields = generate_subscription_fields(&cache, &generated);
1056 assert!(!sub_fields.is_empty(), "Should have subscription fields");
1057
1058 let result = build_dynamic_schema(&generated, &cache, Some(&sub_fields));
1060 assert!(result.is_ok(), "Schema with subscriptions should build");
1061 }
1062
1063 #[test]
1064 fn test_subscription_field_generation() {
1065 let cache = create_test_schema_cache();
1066 let config = SchemaConfig::default();
1067 let generated = build_schema(&cache, &config);
1068
1069 let fields = generate_subscription_fields(&cache, &generated);
1070
1071 assert_eq!(fields.len(), 1);
1073 assert_eq!(fields[0].name, "users");
1074 assert_eq!(fields[0].table_name, "users");
1075 assert_eq!(fields[0].channel_name(), "postrust_public_users");
1076 }
1077
1078 #[test]
1079 fn test_create_subscription_type() {
1080 use crate::subscription::SubscriptionField as SubField;
1081
1082 let fields = vec![
1083 SubField::for_table("public", "users", "Users"),
1084 SubField::for_table("public", "orders", "Orders"),
1085 ];
1086
1087 let _subscription = create_subscription_type(&fields);
1088 }
1090}