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}