sqlx_core/config/mod.rs
1//! (Exported for documentation only) Guide and reference for `sqlx.toml` files.
2//!
3//! To use, create a `sqlx.toml` file in your crate root (the same directory as your `Cargo.toml`).
4//! The configuration in a `sqlx.toml` configures SQLx *only* for the current crate.
5//!
6//! Requires the `sqlx-toml` feature (not enabled by default).
7//!
8//! `sqlx-cli` will also read `sqlx.toml` when running migrations.
9//!
10//! See the [`Config`] type and its fields for individual configuration options.
11//!
12//! See the [reference][`_reference`] for the full `sqlx.toml` file.
13
14use std::error::Error;
15use std::fmt::Debug;
16use std::io;
17use std::path::{Path, PathBuf};
18
19/// Configuration shared by multiple components.
20///
21/// See [`common::Config`] for details.
22pub mod common;
23
24pub mod drivers;
25
26/// Configuration for the `query!()` family of macros.
27///
28/// See [`macros::Config`] for details.
29pub mod macros;
30
31/// Configuration for migrations when executed using `sqlx::migrate!()` or through `sqlx-cli`.
32///
33/// See [`migrate::Config`] for details.
34pub mod migrate;
35
36/// Reference for `sqlx.toml` files
37///
38/// Source: `sqlx-core/src/config/reference.toml`
39///
40/// ```toml
41#[doc = include_str!("reference.toml")]
42/// ```
43pub mod _reference {}
44
45#[cfg(all(test, feature = "sqlx-toml"))]
46mod tests;
47
48/// The parsed structure of a `sqlx.toml` file.
49#[derive(Debug, Default)]
50#[cfg_attr(
51    feature = "sqlx-toml",
52    derive(serde::Deserialize),
53    serde(default, rename_all = "kebab-case", deny_unknown_fields)
54)]
55pub struct Config {
56    /// Configuration shared by multiple components.
57    ///
58    /// See [`common::Config`] for details.
59    pub common: common::Config,
60
61    /// Configuration for database drivers.
62    ///
63    /// See [`drivers::Config`] for details.
64    pub drivers: drivers::Config,
65
66    /// Configuration for the `query!()` family of macros.
67    ///
68    /// See [`macros::Config`] for details.
69    pub macros: macros::Config,
70
71    /// Configuration for migrations when executed using `sqlx::migrate!()` or through `sqlx-cli`.
72    ///
73    /// See [`migrate::Config`] for details.
74    pub migrate: migrate::Config,
75}
76
77/// Error returned from various methods of [`Config`].
78#[derive(thiserror::Error, Debug)]
79pub enum ConfigError {
80    /// The loading method expected `CARGO_MANIFEST_DIR` to be set and it wasn't.
81    ///
82    /// This is necessary to locate the root of the crate currently being compiled.
83    ///
84    /// See [the "Environment Variables" page of the Cargo Book][cargo-env] for details.
85    ///
86    /// [cargo-env]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
87    #[error("environment variable `CARGO_MANIFEST_DIR` must be set and valid")]
88    Env(
89        #[from]
90        #[source]
91        std::env::VarError,
92    ),
93
94    /// No configuration file was found. Not necessarily fatal.
95    #[error("config file {path:?} not found")]
96    NotFound { path: PathBuf },
97
98    /// An I/O error occurred while attempting to read the config file at `path`.
99    ///
100    /// If the error is [`io::ErrorKind::NotFound`], [`Self::NotFound`] is returned instead.
101    #[error("error reading config file {path:?}")]
102    Io {
103        path: PathBuf,
104        #[source]
105        error: io::Error,
106    },
107
108    /// An error in the TOML was encountered while parsing the config file at `path`.
109    ///
110    /// The error gives line numbers and context when printed with `Display`/`ToString`.
111    ///
112    /// Only returned if the `sqlx-toml` feature is enabled.
113    #[error("error parsing config file {path:?}")]
114    Parse {
115        path: PathBuf,
116        /// Type-erased [`toml::de::Error`].
117        #[source]
118        error: Box<dyn Error + Send + Sync + 'static>,
119    },
120
121    /// A `sqlx.toml` file was found or specified, but the `sqlx-toml` feature is not enabled.
122    #[error("SQLx found config file at {path:?} but the `sqlx-toml` feature was not enabled")]
123    ParseDisabled { path: PathBuf },
124}
125
126impl ConfigError {
127    /// Create a [`ConfigError`] from a [`std::io::Error`].
128    ///
129    /// Maps to either `NotFound` or `Io`.
130    pub fn from_io(path: impl Into<PathBuf>, error: io::Error) -> Self {
131        if error.kind() == io::ErrorKind::NotFound {
132            Self::NotFound { path: path.into() }
133        } else {
134            Self::Io {
135                path: path.into(),
136                error,
137            }
138        }
139    }
140
141    /// If this error means the file was not found, return the path that was attempted.
142    pub fn not_found_path(&self) -> Option<&Path> {
143        if let Self::NotFound { path } = self {
144            Some(path)
145        } else {
146            None
147        }
148    }
149}
150
151/// Internal methods for loading a `Config`.
152#[allow(clippy::result_large_err)]
153impl Config {
154    /// Read `$CARGO_MANIFEST_DIR/sqlx.toml` or return `Config::default()` if it does not exist.
155    ///
156    /// # Errors
157    /// * If `CARGO_MANIFEST_DIR` is not set.
158    /// * If the file exists but could not be read or parsed.
159    /// * If the file exists but the `sqlx-toml` feature is disabled.
160    pub fn try_from_crate_or_default() -> Result<Self, ConfigError> {
161        Self::try_from_path_or_default(get_crate_path()?)
162    }
163
164    /// Attempt to read `Config` from the path given, or return `Config::default()` if it does not exist.
165    ///
166    /// # Errors
167    /// * If the file exists but could not be read or parsed.
168    /// * If the file exists but the `sqlx-toml` feature is disabled.
169    pub fn try_from_path_or_default(path: PathBuf) -> Result<Self, ConfigError> {
170        Self::read_from(path).or_else(|e| {
171            if let ConfigError::NotFound { .. } = e {
172                Ok(Config::default())
173            } else {
174                Err(e)
175            }
176        })
177    }
178
179    /// Attempt to read `Config` from the path given.
180    ///
181    /// # Errors
182    /// * If the file does not exist.
183    /// * If the file exists but could not be read or parsed.
184    /// * If the file exists but the `sqlx-toml` feature is disabled.
185    pub fn try_from_path(path: PathBuf) -> Result<Self, ConfigError> {
186        Self::read_from(path)
187    }
188
189    #[cfg(feature = "sqlx-toml")]
190    fn read_from(path: PathBuf) -> Result<Self, ConfigError> {
191        // The `toml` crate doesn't provide an incremental reader.
192        let toml_s = match std::fs::read_to_string(&path) {
193            Ok(toml) => toml,
194            Err(error) => {
195                return Err(ConfigError::from_io(path, error));
196            }
197        };
198
199        // TODO: parse and lint TOML structure before deserializing
200        // Motivation: https://github.com/toml-rs/toml/issues/761
201        tracing::debug!("read config TOML from {path:?}:\n{toml_s}");
202
203        toml::from_str(&toml_s).map_err(|error| ConfigError::Parse {
204            path,
205            error: Box::new(error),
206        })
207    }
208
209    #[cfg(not(feature = "sqlx-toml"))]
210    fn read_from(path: PathBuf) -> Result<Self, ConfigError> {
211        match path.try_exists() {
212            Ok(true) => Err(ConfigError::ParseDisabled { path }),
213            Ok(false) => Err(ConfigError::NotFound { path }),
214            Err(e) => Err(ConfigError::from_io(path, e)),
215        }
216    }
217}
218
219fn get_crate_path() -> Result<PathBuf, ConfigError> {
220    let mut path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
221    path.push("sqlx.toml");
222    Ok(path)
223}