stygian_graph/ports/data_source.rs
1//! Database source port — query databases as pipeline data sources.
2//!
3//! Defines the [`DataSourcePort`](crate::ports::data_source::DataSourcePort) trait for executing queries against
4//! relational or document databases and returning results as
5//! [`serde_json::Value`] rows.
6//!
7//! # Architecture
8//!
9//! ```text
10//! stygian-graph
11//! ├─ DataSourcePort (this file) ← always compiled
12//! └─ Adapters (adapters/)
13//! └─ DatabaseSource (feature="postgres") → sqlx PgPool
14//! ```
15//!
16//! # Example
17//!
18//! ```no_run
19//! use stygian_graph::ports::data_source::{DataSourcePort, QueryParams};
20//! use serde_json::json;
21//!
22//! async fn query<D: DataSourcePort>(db: &D) {
23//! let params = QueryParams {
24//! query: "SELECT id, name FROM users WHERE active = $1".into(),
25//! parameters: vec![json!(true)],
26//! limit: Some(100),
27//! };
28//! let rows = db.query(params).await.unwrap();
29//! for row in &rows {
30//! println!("{row}");
31//! }
32//! }
33//! ```
34
35use crate::domain::error::Result;
36use async_trait::async_trait;
37use serde::{Deserialize, Serialize};
38use serde_json::Value;
39
40// ─────────────────────────────────────────────────────────────────────────────
41// QueryParams
42// ─────────────────────────────────────────────────────────────────────────────
43
44/// Parameters for a database query.
45///
46/// # Example
47///
48/// ```
49/// use stygian_graph::ports::data_source::QueryParams;
50/// use serde_json::json;
51///
52/// let params = QueryParams {
53/// query: "SELECT * FROM items WHERE price > $1".into(),
54/// parameters: vec![json!(9.99)],
55/// limit: Some(50),
56/// };
57/// ```
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct QueryParams {
60 /// SQL or query-language statement to execute
61 pub query: String,
62 /// Positional bind parameters
63 pub parameters: Vec<Value>,
64 /// Optional row limit (applied as `LIMIT` or equivalent)
65 pub limit: Option<u64>,
66}
67
68// ─────────────────────────────────────────────────────────────────────────────
69// DataSourcePort
70// ─────────────────────────────────────────────────────────────────────────────
71
72/// Port: query a database and return rows as JSON values.
73///
74/// Implementations connect to PostgreSQL, MySQL, SQLite, MongoDB, or any
75/// other datastore and return results as `Vec<Value>`.
76///
77/// # Example
78///
79/// ```no_run
80/// use stygian_graph::ports::data_source::{DataSourcePort, QueryParams};
81/// use stygian_graph::domain::error::Result;
82/// use async_trait::async_trait;
83/// use serde_json::{json, Value};
84///
85/// struct MockDb;
86///
87/// #[async_trait]
88/// impl DataSourcePort for MockDb {
89/// async fn query(&self, params: QueryParams) -> Result<Vec<Value>> {
90/// Ok(vec![json!({"id": 1, "name": "test"})])
91/// }
92///
93/// async fn healthcheck(&self) -> Result<()> {
94/// Ok(())
95/// }
96///
97/// fn source_name(&self) -> &str {
98/// "mock-db"
99/// }
100/// }
101/// ```
102#[async_trait]
103pub trait DataSourcePort: Send + Sync {
104 /// Execute a query and return results as JSON rows.
105 ///
106 /// # Arguments
107 ///
108 /// * `params` - Query statement, bind parameters, and optional limit
109 ///
110 /// # Returns
111 ///
112 /// * `Ok(Vec<Value>)` - Result rows serialised as JSON objects
113 /// * `Err(StygianError)` - Query or connection error
114 ///
115 /// # Example
116 ///
117 /// ```no_run
118 /// # use stygian_graph::ports::data_source::{DataSourcePort, QueryParams};
119 /// # use serde_json::json;
120 /// # async fn example(db: impl DataSourcePort) {
121 /// let params = QueryParams {
122 /// query: "SELECT 1 AS n".into(),
123 /// parameters: vec![],
124 /// limit: None,
125 /// };
126 /// let rows = db.query(params).await.unwrap();
127 /// # }
128 /// ```
129 async fn query(&self, params: QueryParams) -> Result<Vec<Value>>;
130
131 /// Check that the underlying connection is alive.
132 ///
133 /// # Example
134 ///
135 /// ```no_run
136 /// # use stygian_graph::ports::data_source::DataSourcePort;
137 /// # async fn example(db: impl DataSourcePort) {
138 /// db.healthcheck().await.unwrap();
139 /// # }
140 /// ```
141 async fn healthcheck(&self) -> Result<()>;
142
143 /// Human-readable name of this data source (e.g. `"postgres"`, `"sqlite"`).
144 ///
145 /// # Example
146 ///
147 /// ```no_run
148 /// # use stygian_graph::ports::data_source::DataSourcePort;
149 /// # fn example(db: impl DataSourcePort) {
150 /// println!("Connected to: {}", db.source_name());
151 /// # }
152 /// ```
153 fn source_name(&self) -> &str;
154}