Skip to main content

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}