#![allow(clippy::doc_markdown)] #![allow(clippy::map_unwrap_or)]
use fraiseql_wire::FraiseClient;
use futures::stream::StreamExt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(dead_code)] struct User {
id: String,
name: String,
email: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Project {
id: String,
title: String,
description: Option<String>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("╔════════════════════════════════════════════════════════════════╗");
println!("║ fraiseql-wire: Typed Streaming Example ║");
println!("║ ║");
println!("║ Type T affects ONLY deserialization, not SQL/filtering ║");
println!("╚════════════════════════════════════════════════════════════════╝\n");
let host = std::env::var("POSTGRES_HOST").unwrap_or_else(|_| "localhost".to_string());
let port = std::env::var("POSTGRES_PORT").unwrap_or_else(|_| "5433".to_string());
let user = std::env::var("POSTGRES_USER").unwrap_or_else(|_| "postgres".to_string());
let password = std::env::var("POSTGRES_PASSWORD").unwrap_or_else(|_| "postgres".to_string());
let db = std::env::var("POSTGRES_DB").unwrap_or_else(|_| "postgres".to_string());
let entity = std::env::var("TEST_ENTITY").unwrap_or_else(|_| "projects".to_string());
let conn_string = format!("postgres://{}:{}@{}:{}/{}", user, password, host, port, db);
println!("📊 Example: Typed Streaming with Type-Safe Deserialization\n");
println!("Connection: {}@{}:{}/{}", user, host, port, db);
println!("Entity: {}\n", entity);
example_typed_query(&conn_string, &entity).await?;
example_raw_json(&conn_string, &entity).await?;
example_with_sql_predicate(&conn_string, &entity).await?;
example_with_rust_predicate(&conn_string, &entity).await?;
example_type_transparency(&conn_string, &entity).await?;
println!("\n✨ All examples completed successfully!");
println!("Key takeaway: Type T affects only deserialization, not SQL/filtering/ordering.\n");
Ok(())
}
async fn example_typed_query(
conn_string: &str,
entity: &str,
) -> Result<(), Box<dyn std::error::Error>> {
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Example 1: Type-Safe Query with Custom Struct");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
let client = FraiseClient::connect(conn_string).await?;
println!(
"Building typed query: client.query::<Project>(\"{}\")",
entity
);
println!("Type T = Project (custom struct)\n");
let mut stream = client
.query::<Project>(entity)
.chunk_size(32)
.execute()
.await?;
println!("✓ Query started, streaming with type-safe deserialization:\n");
let mut count = 0;
while let Some(result) = stream.next().await {
match result {
Ok(project) => {
count += 1;
println!(" [{:2}] {} - {}", count, project.id, project.title);
if let Some(desc) = project.description {
println!(" Description: {}", desc);
}
if count >= 10 {
println!(" ... (limiting to first 10 for demo)");
break;
}
}
Err(e) => {
eprintln!("✗ Deserialization error: {}", e);
return Err(Box::new(e));
}
}
}
println!("\n✓ Type-safe example: Received {} typed items\n", count);
Ok(())
}
async fn example_raw_json(
conn_string: &str,
entity: &str,
) -> Result<(), Box<dyn std::error::Error>> {
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Example 2: Raw JSON Escape Hatch (Forward Compatibility)");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
let client = FraiseClient::connect(conn_string).await?;
println!(
"Building raw JSON query: client.query::<Value>(\"{}\")",
entity
);
println!("Type T = serde_json::Value (raw JSON)\n");
let mut stream = client
.query::<serde_json::Value>(entity)
.chunk_size(32)
.execute()
.await?;
println!("✓ Query started, streaming raw JSON:\n");
let mut count = 0;
while let Some(result) = stream.next().await {
match result {
Ok(json) => {
count += 1;
let id = json["id"].as_str().unwrap_or("?");
let title = json["title"].as_str().unwrap_or("?");
println!(" [{:2}] {} - {}", count, id, title);
if count >= 5 {
println!(" ... (limiting to first 5 for demo)");
break;
}
}
Err(e) => {
eprintln!("✗ Error: {}", e);
return Err(Box::new(e));
}
}
}
println!(
"\n✓ Escape hatch example: Received {} raw JSON items\n",
count
);
Ok(())
}
async fn example_with_sql_predicate(
conn_string: &str,
entity: &str,
) -> Result<(), Box<dyn std::error::Error>> {
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Example 3: Type-Safe Query with SQL WHERE Predicate");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
let client = FraiseClient::connect(conn_string).await?;
println!("Constraint: Type T does NOT affect SQL WHERE clause\n");
println!("Building query with WHERE predicate:");
println!(" .query::<Project>(\"{}\")", entity);
println!(" .where_sql(\"data->>'title' LIKE 'A%'\")");
println!(" .execute()\n");
let mut stream = client
.query::<Project>(entity)
.where_sql("1 = 1") .chunk_size(32)
.execute()
.await?;
println!("✓ Query started with SQL predicate:\n");
let mut count = 0;
while let Some(result) = stream.next().await {
match result {
Ok(project) => {
count += 1;
println!(" [{:2}] {} - {}", count, project.id, project.title);
if count >= 5 {
println!(" ... (limiting to first 5 for demo)");
break;
}
}
Err(e) => {
eprintln!("✗ Error: {}", e);
return Err(Box::new(e));
}
}
}
println!(
"\n✓ SQL predicate example: Received {} typed items\n",
count
);
println!("Key point: SQL WHERE applied on server BEFORE deserialization to T\n");
Ok(())
}
async fn example_with_rust_predicate(
conn_string: &str,
entity: &str,
) -> Result<(), Box<dyn std::error::Error>> {
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Example 4: Type-Safe Query with Rust-Side Predicate");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
let client = FraiseClient::connect(conn_string).await?;
println!("Constraint: Type T does NOT affect Rust predicate\n");
println!("Building query with Rust-side filter:");
println!(" .query::<Project>(\"{}\")", entity);
println!(" .where_rust(|json| {{ /* json is Value, not T */ }})");
println!(" .execute()\n");
let mut stream = client
.query::<Project>(entity)
.where_rust(|json| {
json["id"]
.as_str()
.map(|id| id.contains('1'))
.unwrap_or(false)
})
.chunk_size(32)
.execute()
.await?;
println!("✓ Query started with Rust predicate (filtering on 'id' contains '1'):\n");
let mut count = 0;
while let Some(result) = stream.next().await {
match result {
Ok(project) => {
count += 1;
println!(
" [{:2}] {} - {} (matches predicate)",
count, project.id, project.title
);
if count >= 5 {
println!(" ... (limiting to first 5 for demo)");
break;
}
}
Err(e) => {
eprintln!("✗ Error: {}", e);
return Err(Box::new(e));
}
}
}
println!(
"\n✓ Rust predicate example: Received {} filtered items\n",
count
);
println!("Key point: Predicate filters JSON BEFORE deserialization to T\n");
Ok(())
}
async fn example_type_transparency(
conn_string: &str,
entity: &str,
) -> Result<(), Box<dyn std::error::Error>> {
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Example 5: Type Transparency (SQL and Filtering Identical)");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
println!("Constraint: Type T affects ONLY deserialization\n");
println!("Same SQL, different types:\n");
println!("Version 1: Type T = Project (struct)");
let client1 = FraiseClient::connect(conn_string).await?;
let mut stream1 = client1
.query::<Project>(entity)
.chunk_size(32)
.execute()
.await?;
let mut count1 = 0;
while let Some(result) = stream1.next().await {
if result.is_ok() {
count1 += 1;
}
if count1 >= 5 {
break;
}
}
println!(" → Received {} items (as Project structs)\n", count1);
println!("Version 2: Type T = serde_json::Value (raw JSON)");
let client2 = FraiseClient::connect(conn_string).await?;
let mut stream2 = client2
.query::<serde_json::Value>(entity)
.chunk_size(32)
.execute()
.await?;
let mut count2 = 0;
while let Some(result) = stream2.next().await {
if result.is_ok() {
count2 += 1;
}
if count2 >= 5 {
break;
}
}
println!(" → Received {} items (as raw JSON)\n", count2);
println!("Comparison:");
println!(" Same SQL: ✓ (SELECT data FROM v_{})", entity);
println!(" Same filtering: ✓ (none in this example)");
println!(" Same result set: ✓ ({} items each)", count1);
println!(" Different type: ✓ (Project vs Value)\n");
println!("✓ Type transparency verified: T affects only deserialization\n");
Ok(())
}