gel_tokio/
lib.rs

1/*!
2Client for the Gel database, using async Tokio runtime
3
4To get started, check out the [Rust client tutorial](`tutorial`).
5
6The main way to use Gel bindings is to use the [`Client`]. It encompasses
7connection pool to the database that is transparent for user. Individual
8queries can be made via methods on the client. Correlated queries are done
9via [transactions](Client::transaction).
10
11To create a client, use the [`create_client`] function (it gets a database
12connection configuration from environment). You can also use a [`Builder`]
13to [`build`](`Builder::new`) custom [`Config`] and [create a
14client](Client::new) using that config.
15
16# Example
17
18```rust,no_run
19#[tokio::main]
20async fn main() -> anyhow::Result<()> {
21    let conn = gel_tokio::create_client().await?;
22    let val = conn.query_required_single::<i64, _>(
23        "SELECT 7*8",
24        &(),
25    ).await?;
26    println!("7*8 is: {}", val);
27    Ok(())
28}
29```
30More [examples on github](https://github.com/edgedb/edgedb-rust/tree/master/gel-tokio/examples)
31
32# Nice Error Reporting
33
34We use [miette] crate for including snippets in your error reporting code.
35
36To make it work, first you need enable `fancy` feature in your top-level
37crate's `Cargo.toml`:
38```toml
39[dependencies]
40miette = { version="5.3.0", features=["fancy"] }
41gel-tokio = { version="*", features=["miette-errors"] }
42```
43
44Then if you use `miette` all the way through your application, it just
45works:
46```rust,no_run
47#[tokio::main]
48async fn main() -> miette::Result<()> {
49    let conn = gel_tokio::create_client().await?;
50    conn.query::<String, _>("SELECT 1+2)", &()).await?;
51    Ok(())
52}
53```
54
55However, if you use some boxed error container (e.g. [anyhow]), you
56might need to downcast error for printing:
57```rust,no_run
58async fn do_something() -> anyhow::Result<()> {
59    let conn = gel_tokio::create_client().await?;
60    conn.query::<String, _>("SELECT 1+2)", &()).await?;
61    Ok(())
62}
63
64#[tokio::main]
65async fn main() {
66    match do_something().await {
67        Ok(res) => res,
68        Err(e) => {
69            e.downcast::<gel_tokio::Error>()
70                .map(|e| eprintln!("{:?}", miette::Report::new(e)))
71                .unwrap_or_else(|e| eprintln!("{:#}", e));
72            std::process::exit(1);
73        }
74    }
75}
76```
77
78In some cases, where parts of your code use `miette::Result` or
79`miette::Report` before converting to the boxed (anyhow) container, you
80might want a little bit more complex downcasting:
81
82```rust,no_run
83# async fn do_something() -> anyhow::Result<()> { unimplemented!() }
84#[tokio::main]
85async fn main() {
86    match do_something().await {
87        Ok(res) => res,
88        Err(e) => {
89            e.downcast::<gel_tokio::Error>()
90                .map(|e| eprintln!("{:?}", miette::Report::new(e)))
91                .or_else(|e| e.downcast::<miette::Report>()
92                    .map(|e| eprintln!("{:?}", e)))
93                .unwrap_or_else(|e| eprintln!("{:#}", e));
94            std::process::exit(1);
95        }
96    }
97}
98```
99
100Note that last two examples do hide error contexts from anyhow and do not
101pretty print if `source()` of the error is `gel_errors::Error` but not
102the top-level one. We leave those more complex cases as an excersize to the
103reader.
104
105[miette]: https://crates.io/crates/miette
106[anyhow]: https://crates.io/crates/anyhow
107*/
108
109#![cfg_attr(
110    not(feature = "unstable"),
111    warn(missing_docs, missing_debug_implementations)
112)]
113
114macro_rules! unstable_pub_mods {
115    ($(mod $mod_name:ident;)*) => {
116        $(
117            #[cfg(feature = "unstable")]
118            pub mod $mod_name;
119            #[cfg(not(feature = "unstable"))]
120            mod $mod_name;
121        )*
122    }
123}
124
125// If the unstable feature is enabled, the modules will be public.
126// If the unstable feature is not enabled, the modules will be private.
127unstable_pub_mods! {
128    mod builder;
129    mod raw;
130    mod server_params;
131}
132
133#[deprecated(note = "use `dsn` module instead")]
134pub use gel_dsn::gel::{Builder, CloudName, Config, InstanceName, TlsSecurity};
135
136/// Gel data-source name (DSN) parser and builder.
137pub mod dsn {
138    pub use gel_dsn::gel::*;
139    pub use gel_dsn::{Host, HostType};
140}
141
142mod client;
143mod errors;
144mod options;
145mod query_executor;
146mod sealed;
147pub mod state;
148mod transaction;
149pub mod tutorial;
150
151#[cfg(feature = "derive")]
152pub use gel_derive::{ConfigDelta, GlobalsDelta, Queryable};
153
154pub use client::Client;
155pub use errors::Error;
156pub use options::{RetryCondition, RetryOptions, TransactionOptions};
157pub use query_executor::{QueryExecutor, ResultVerbose};
158pub use state::{ConfigDelta, GlobalsDelta};
159pub use transaction::{RetryingTransaction, Transaction};
160
161/// The ordered list of project filenames supported.
162pub const PROJECT_FILES: &[&str] = &["gel.toml", "edgedb.toml"];
163
164/// The default project filename.
165pub const DEFAULT_PROJECT_FILE: &str = PROJECT_FILES[0];
166
167#[cfg(feature = "unstable")]
168pub use transaction::RawTransaction;
169
170/// Create a connection to the database with default parameters
171///
172/// It's expected that connection parameters are set up using environment
173/// (either environment variables or project configuration in a file named by
174/// [`PROJECT_FILES`]) so no configuration is specified here.
175///
176/// This method tries to esablish single connection immediately to ensure that
177/// configuration is valid and will error out otherwise.
178///
179/// For more fine-grained setup see [`Client`] and [`Builder`] documentation and
180/// the source of this function.
181#[cfg(feature = "env")]
182pub async fn create_client() -> Result<Client, Error> {
183    use gel_errors::{ClientConnectionError, ErrorKind};
184    use tokio::task::spawn_blocking;
185
186    // Run the builder in a blocking context (it's unlikely to pause much but
187    // better to be safe)
188    let config = spawn_blocking(|| Builder::default().build())
189        .await
190        .map_err(ClientConnectionError::with_source)??;
191    let pool = Client::new(&config);
192    pool.ensure_connected().await?;
193    Ok(pool)
194}