diff --git a/Cargo.toml b/Cargo.toml index 72a9d01c..cf3352ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,16 +54,14 @@ features = ["all-databases", "_unstable-all-types", "_unstable-doc"] rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["any", "macros", "migrate", "json", "config-all"] +default = ["any", "macros", "migrate", "json", "sqlx-toml"] derive = ["sqlx-macros/derive"] macros = ["derive", "sqlx-macros/macros"] migrate = ["sqlx-core/migrate", "sqlx-macros?/migrate", "sqlx-mysql?/migrate", "sqlx-postgres?/migrate", "sqlx-sqlite?/migrate"] -# Enable parsing of `sqlx.toml` for configuring macros, migrations, or both. -config-macros = ["sqlx-macros?/config-macros"] -config-migrate = ["sqlx-macros?/config-migrate"] -config-all = ["config-macros", "config-migrate"] +# Enable parsing of `sqlx.toml` for configuring macros and migrations. +sqlx-toml = ["sqlx-core/sqlx-toml", "sqlx-macros?/sqlx-toml"] # intended mainly for CI and docs all-databases = ["mysql", "sqlite", "postgres", "any"] @@ -79,7 +77,7 @@ _unstable-all-types = [ "bit-vec", ] # Render documentation that wouldn't otherwise be shown (e.g. `sqlx_core::config`). -_unstable-doc = ["config-all", "sqlx-core/_unstable-doc"] +_unstable-doc = [] # Base runtime features without TLS runtime-async-std = ["_rt-async-std", "sqlx-core/_rt-async-std", "sqlx-macros?/_rt-async-std"] diff --git a/sqlx-cli/Cargo.toml b/sqlx-cli/Cargo.toml index 0b047ab1..4ece2263 100644 --- a/sqlx-cli/Cargo.toml +++ b/sqlx-cli/Cargo.toml @@ -49,7 +49,8 @@ filetime = "0.2" backoff = { version = "0.4.0", features = ["futures", "tokio"] } [features] -default = ["postgres", "sqlite", "mysql", "native-tls", "completions"] +default = ["postgres", "sqlite", "mysql", "native-tls", "completions", "sqlx-toml"] + rustls = ["sqlx/runtime-tokio-rustls"] native-tls = ["sqlx/runtime-tokio-native-tls"] @@ -64,6 +65,8 @@ openssl-vendored = ["openssl/vendored"] completions = ["dep:clap_complete"] +sqlx-toml = ["sqlx/sqlx-toml"] + [dev-dependencies] assert_cmd = "2.0.11" tempfile = "3.10.1" diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index f70adde5..ee6e344e 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -12,7 +12,7 @@ features = ["offline"] [features] default = [] -migrate = ["sha2", "crc", "config-migrate"] +migrate = ["sha2", "crc"] any = [] @@ -31,11 +31,13 @@ _tls-none = [] # support offline/decoupled building (enables serialization of `Describe`) offline = ["serde", "either/serde"] -config = ["serde", "toml/parse"] -config-macros = ["config"] -config-migrate = ["config"] +# Enable parsing of `sqlx.toml`. +# For simplicity, the `config` module is always enabled, +# but disabling this disables the `serde` derives and the `toml` crate, +# which is a good bit less code to compile if the feature isn't being used. +sqlx-toml = ["serde", "toml/parse"] -_unstable-doc = ["config-macros", "config-migrate"] +_unstable-doc = ["sqlx-toml"] [dependencies] # Runtimes diff --git a/sqlx-core/src/config/common.rs b/sqlx-core/src/config/common.rs index 8c774fc6..1468f24a 100644 --- a/sqlx-core/src/config/common.rs +++ b/sqlx-core/src/config/common.rs @@ -1,5 +1,6 @@ /// Configuration shared by multiple components. -#[derive(Debug, Default, serde::Deserialize)] +#[derive(Debug, Default)] +#[cfg_attr(feature = "sqlx-toml", derive(serde::Deserialize))] pub struct Config { /// Override the database URL environment variable. /// @@ -36,3 +37,9 @@ pub struct Config { /// and the ones used in `bar` will use `BAR_DATABASE_URL`. pub database_url_var: Option, } + +impl Config { + pub fn database_url_var(&self) -> &str { + self.database_url_var.as_deref().unwrap_or("DATABASE_URL") + } +} \ No newline at end of file diff --git a/sqlx-core/src/config/macros.rs b/sqlx-core/src/config/macros.rs index 5edd30dc..142f059d 100644 --- a/sqlx-core/src/config/macros.rs +++ b/sqlx-core/src/config/macros.rs @@ -1,8 +1,8 @@ use std::collections::BTreeMap; /// Configuration for the `query!()` family of macros. -#[derive(Debug, Default, serde::Deserialize)] -#[serde(default)] +#[derive(Debug, Default)] +#[cfg_attr(feature = "sqlx-toml", derive(serde::Deserialize), serde(default))] pub struct Config { /// Specify the crate to use for mapping date/time types to Rust. /// @@ -235,8 +235,12 @@ pub struct Config { } /// The crate to use for mapping date/time types to Rust. -#[derive(Debug, Default, PartialEq, Eq, serde::Deserialize)] -#[serde(rename_all = "snake_case")] +#[derive(Debug, Default, PartialEq, Eq)] +#[cfg_attr( + feature = "sqlx-toml", + derive(serde::Deserialize), + serde(rename_all = "snake_case") +)] pub enum DateTimeCrate { /// Use whichever crate is enabled (`time` then `chrono`). #[default] diff --git a/sqlx-core/src/config/migrate.rs b/sqlx-core/src/config/migrate.rs index 5878f9a2..efc03a01 100644 --- a/sqlx-core/src/config/migrate.rs +++ b/sqlx-core/src/config/migrate.rs @@ -12,8 +12,8 @@ use std::collections::BTreeSet; /// if the proper precautions are not taken. /// /// Be sure you know what you are doing and that you read all relevant documentation _thoroughly_. -#[derive(Debug, Default, serde::Deserialize)] -#[serde(default)] +#[derive(Debug, Default)] +#[cfg_attr(feature = "sqlx-toml", derive(serde::Deserialize), serde(default))] pub struct Config { /// Override the name of the table used to track executed migrations. /// @@ -118,8 +118,12 @@ pub struct Config { } /// The default type of migration that `sqlx migrate create` should create by default. -#[derive(Debug, Default, PartialEq, Eq, serde::Deserialize)] -#[serde(rename_all = "snake_case")] +#[derive(Debug, Default, PartialEq, Eq)] +#[cfg_attr( + feature = "sqlx-toml", + derive(serde::Deserialize), + serde(rename_all = "snake_case") +)] pub enum DefaultMigrationType { /// Create the same migration type as that of the latest existing migration, /// or `Simple` otherwise. @@ -134,8 +138,12 @@ pub enum DefaultMigrationType { } /// The default scheme that `sqlx migrate create` should use for version integers. -#[derive(Debug, Default, PartialEq, Eq, serde::Deserialize)] -#[serde(rename_all = "snake_case")] +#[derive(Debug, Default, PartialEq, Eq)] +#[cfg_attr( + feature = "sqlx-toml", + derive(serde::Deserialize), + serde(rename_all = "snake_case") +)] pub enum DefaultVersioning { /// Infer the versioning scheme from existing migrations: /// diff --git a/sqlx-core/src/config/mod.rs b/sqlx-core/src/config/mod.rs index 97947724..3bbde5c2 100644 --- a/sqlx-core/src/config/mod.rs +++ b/sqlx-core/src/config/mod.rs @@ -7,6 +7,7 @@ //! //! See the [reference][`_reference`] for the full `sqlx.toml` file. +use std::error::Error; use std::fmt::Debug; use std::io; use std::path::{Path, PathBuf}; @@ -23,13 +24,11 @@ pub mod common; /// Configuration for the `query!()` family of macros. /// /// See [`macros::Config`] for details. -#[cfg(feature = "config-macros")] pub mod macros; /// Configuration for migrations when executed using `sqlx::migrate!()` or through `sqlx-cli`. /// /// See [`migrate::Config`] for details. -#[cfg(feature = "config-migrate")] pub mod migrate; /// Reference for `sqlx.toml` files @@ -41,11 +40,12 @@ pub mod migrate; /// ``` pub mod _reference {} -#[cfg(test)] +#[cfg(all(test, feature = "sqlx-toml"))] mod tests; /// The parsed structure of a `sqlx.toml` file. -#[derive(Debug, Default, serde::Deserialize)] +#[derive(Debug, Default)] +#[cfg_attr(feature = "sqlx-toml", derive(serde::Deserialize))] pub struct Config { /// Configuration shared by multiple components. /// @@ -55,21 +55,11 @@ pub struct Config { /// Configuration for the `query!()` family of macros. /// /// See [`macros::Config`] for details. - #[cfg_attr( - docsrs, - doc(cfg(any(feature = "config-all", feature = "config-macros"))) - )] - #[cfg(feature = "config-macros")] pub macros: macros::Config, /// Configuration for migrations when executed using `sqlx::migrate!()` or through `sqlx-cli`. /// /// See [`migrate::Config`] for details. - #[cfg_attr( - docsrs, - doc(cfg(any(feature = "config-all", feature = "config-migrate"))) - )] - #[cfg(feature = "config-migrate")] pub migrate: migrate::Config, } @@ -90,13 +80,17 @@ pub enum ConfigError { std::env::VarError, ), + /// No configuration file was found. Not necessarily fatal. + #[error("config file {path:?} not found")] + NotFound { + path: PathBuf, + }, + /// An I/O error occurred while attempting to read the config file at `path`. /// - /// This includes [`io::ErrorKind::NotFound`]. - /// - /// [`Self::not_found_path()`] will return the path if the file was not found. + /// If the error is [`io::ErrorKind::NotFound`], [`Self::NotFound`] is returned instead. #[error("error reading config file {path:?}")] - Read { + Io { path: PathBuf, #[source] error: io::Error, @@ -105,22 +99,41 @@ pub enum ConfigError { /// An error in the TOML was encountered while parsing the config file at `path`. /// /// The error gives line numbers and context when printed with `Display`/`ToString`. + /// + /// Only returned if the `sqlx-toml` feature is enabled. #[error("error parsing config file {path:?}")] Parse { path: PathBuf, + /// Type-erased [`toml::de::Error`]. #[source] - error: toml::de::Error, + error: Box, + }, + + /// A `sqlx.toml` file was found or specified, but the `sqlx-toml` feature is not enabled. + #[error("SQLx found config file at {path:?} but the `sqlx-toml` feature was not enabled")] + ParseDisabled { + path: PathBuf }, } impl ConfigError { + /// Create a [`ConfigError`] from a [`std::io::Error`]. + /// + /// Maps to either `NotFound` or `Io`. + pub fn from_io(path: PathBuf, error: io::Error) -> Self { + if error.kind() == io::ErrorKind::NotFound { + Self::NotFound { path } + } else { + Self::Io { path, error } + } + } + /// If this error means the file was not found, return the path that was attempted. pub fn not_found_path(&self) -> Option<&Path> { - match self { - ConfigError::Read { path, error } if error.kind() == io::ErrorKind::NotFound => { - Some(path) - } - _ => None, + if let Self::NotFound { path } = self { + Some(path) + } else { + None } } } @@ -140,14 +153,22 @@ impl Config { /// If the file exists but an unrecoverable error was encountered while parsing it. pub fn from_crate() -> &'static Self { Self::try_from_crate().unwrap_or_else(|e| { - if let Some(path) = e.not_found_path() { - // Non-fatal - tracing::debug!("Not reading config, file {path:?} not found (error: {e})"); - CACHE.get_or_init(Config::default) - } else { + match e { + ConfigError::NotFound { path } => { + // Non-fatal + tracing::debug!("Not reading config, file {path:?} not found"); + CACHE.get_or_init(Config::default) + } + // FATAL ERRORS BELOW: // In the case of migrations, // we can't proceed with defaults as they may be completely wrong. - panic!("failed to read sqlx config: {e}") + e @ ConfigError::ParseDisabled { .. } => { + // Only returned if the file exists but the feature is not enabled. + panic!("{e}") + } + e => { + panic!("failed to read sqlx config: {e}") + } } }) } @@ -188,12 +209,13 @@ impl Config { }) } + #[cfg(feature = "sqlx-toml")] fn read_from(path: PathBuf) -> Result { // The `toml` crate doesn't provide an incremental reader. let toml_s = match std::fs::read_to_string(&path) { Ok(toml) => toml, Err(error) => { - return Err(ConfigError::Read { path, error }); + return Err(ConfigError::from_io(path, error)); } }; @@ -201,6 +223,15 @@ impl Config { // Motivation: https://github.com/toml-rs/toml/issues/761 tracing::debug!("read config TOML from {path:?}:\n{toml_s}"); - toml::from_str(&toml_s).map_err(|error| ConfigError::Parse { path, error }) + toml::from_str(&toml_s).map_err(|error| ConfigError::Parse { path, error: Box::new(error) }) + } + + #[cfg(not(feature = "sqlx-toml"))] + fn read_from(path: PathBuf) -> Result { + match path.try_exists() { + Ok(true) => Err(ConfigError::ParseDisabled { path }), + Ok(false) => Err(ConfigError::NotFound { path }), + Err(e) => Err(ConfigError::from_io(path, e)) + } } } diff --git a/sqlx-core/src/config/tests.rs b/sqlx-core/src/config/tests.rs index 521e7074..e5033bb4 100644 --- a/sqlx-core/src/config/tests.rs +++ b/sqlx-core/src/config/tests.rs @@ -20,7 +20,6 @@ fn assert_common_config(config: &config::common::Config) { assert_eq!(config.database_url_var.as_deref(), Some("FOO_DATABASE_URL")); } -#[cfg(feature = "config-macros")] fn assert_macros_config(config: &config::macros::Config) { use config::macros::*; @@ -74,7 +73,6 @@ fn assert_macros_config(config: &config::macros::Config) { ); } -#[cfg(feature = "config-migrate")] fn assert_migrate_config(config: &config::migrate::Config) { use config::migrate::*; diff --git a/sqlx-core/src/lib.rs b/sqlx-core/src/lib.rs index 8b831eca..09f2900b 100644 --- a/sqlx-core/src/lib.rs +++ b/sqlx-core/src/lib.rs @@ -91,7 +91,6 @@ pub mod any; #[cfg(feature = "migrate")] pub mod testing; -#[cfg(feature = "config")] pub mod config; pub use error::{Error, Result}; diff --git a/sqlx-macros-core/Cargo.toml b/sqlx-macros-core/Cargo.toml index 46b7dfbf..ad1a8e18 100644 --- a/sqlx-macros-core/Cargo.toml +++ b/sqlx-macros-core/Cargo.toml @@ -26,9 +26,7 @@ derive = [] macros = [] migrate = ["sqlx-core/migrate"] -config = ["sqlx-core/config"] -config-macros = ["config", "sqlx-core/config-macros"] -config-migrate = ["config", "sqlx-core/config-migrate"] +sqlx-toml = ["sqlx-core/sqlx-toml"] # database mysql = ["sqlx-mysql"] diff --git a/sqlx-macros-core/src/query/mod.rs b/sqlx-macros-core/src/query/mod.rs index 09acff9b..190d272d 100644 --- a/sqlx-macros-core/src/query/mod.rs +++ b/sqlx-macros-core/src/query/mod.rs @@ -16,6 +16,7 @@ use crate::query::data::{hash_string, DynQueryData, QueryData}; use crate::query::input::RecordType; use either::Either; use url::Url; +use sqlx_core::config::Config; mod args; mod data; @@ -138,8 +139,12 @@ static METADATA: Lazy = Lazy::new(|| { let offline = env("SQLX_OFFLINE") .map(|s| s.eq_ignore_ascii_case("true") || s == "1") .unwrap_or(false); - - let database_url = env("DATABASE_URL").ok(); + + let var_name = Config::from_crate() + .common + .database_url_var(); + + let database_url = env(var_name).ok(); Metadata { manifest_dir, diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index 1d1b0bcd..6792af6e 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -27,8 +27,7 @@ derive = ["sqlx-macros-core/derive"] macros = ["sqlx-macros-core/macros"] migrate = ["sqlx-macros-core/migrate"] -config-macros = ["sqlx-macros-core/config-macros"] -config-migrate = ["sqlx-macros-core/config-migrate"] +sqlx-toml = ["sqlx-macros-core/sqlx-toml"] # database mysql = ["sqlx-macros-core/mysql"]