prepare for updated sqlx

This commit is contained in:
Robin Appelman 2023-11-30 20:38:03 +01:00
commit 25721b7f98
3 changed files with 259 additions and 146 deletions

View file

@ -1,7 +1,7 @@
[package]
name = "nextcloud-config-parser"
description = "Rust parser for nextcloud config files"
version = "0.8.0"
version = "0.9.0"
authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2021"
license = "MIT OR Apache-2.0"
@ -10,15 +10,18 @@ documentation = "https://docs.rs/nextcloud-config-parser"
rust-version = "1.58"
[dependencies]
redis = { version = "0.23.0", optional = true, default-features = false }
thiserror = "1.0.40"
redis = { version = "0.23.3", optional = true, default-features = false }
thiserror = "1.0.50"
php-literal-parser = "0.5.1"
sqlx = { version = "0.6.3", default-features = false, features = ["runtime-tokio-rustls", "any", "mysql", "sqlite", "postgres"], optional = true }
miette = "5.8.0"
sqlx = { version = "0.7.3", default-features = false, features = ["runtime-tokio-rustls", "any", "mysql", "sqlite", "postgres"], optional = true }
miette = "5.10.0"
futures-core = "0.3.29"
urlencoding = "2.1.3"
form_urlencoded = "1.2.1"
[dev-dependencies]
sqlx = { version = "0.6.3", default-features = false, features = ["runtime-tokio-rustls", "any", "mysql", "sqlite", "postgres"] }
miette = { version = "5.8.0", features = ["fancy"] }
sqlx = { version = "0.7.1", default-features = false, features = ["runtime-tokio-rustls", "any", "mysql", "sqlite", "postgres"] }
miette = { version = "5.10.0", features = ["fancy"] }
[features]
db-sqlx = ["sqlx"]

View file

@ -1,5 +1,6 @@
mod nc;
use form_urlencoded::Serializer;
use miette::Diagnostic;
#[cfg(feature = "redis-connect")]
use redis::{ConnectionAddr, ConnectionInfo};
@ -120,7 +121,7 @@ pub enum NotAConfigError {
NotAnArray(PathBuf),
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum SslOptions {
Enabled {
key: String,
@ -132,7 +133,7 @@ pub enum SslOptions {
Default,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Database {
Sqlite {
database: PathBuf,
@ -153,24 +154,17 @@ pub enum Database {
},
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum DbConnect {
Tcp { host: String, port: u16 },
Socket(PathBuf),
}
#[cfg(feature = "db-sqlx")]
impl From<Database> for sqlx::any::AnyConnectOptions {
fn from(cfg: Database) -> Self {
use sqlx::{
mysql::{MySqlConnectOptions, MySqlSslMode},
postgres::{PgConnectOptions, PgSslMode},
sqlite::SqliteConnectOptions,
};
match cfg {
impl Database {
pub fn url<'a>(&self) -> String {
match self {
Database::Sqlite { database } => {
SqliteConnectOptions::default().filename(database).into()
format!("sqlite://{}", database.display())
}
Database::MySql {
database,
@ -179,34 +173,57 @@ impl From<Database> for sqlx::any::AnyConnectOptions {
connect,
ssl_options,
} => {
let mut options = MySqlConnectOptions::default()
.database(&database)
.username(&username)
.password(&password);
let mut params = Serializer::new(String::new());
match ssl_options {
SslOptions::Enabled { ca, verify, .. } => {
options = options.ssl_ca(ca);
options = options.ssl_mode(if verify {
MySqlSslMode::VerifyIdentity
} else {
MySqlSslMode::VerifyCa
});
}
SslOptions::Disabled => {
options = options.ssl_mode(MySqlSslMode::Disabled);
}
SslOptions::Default => {}
SslOptions::Disabled => {
params.append_pair("ssl-mode", "disabled");
}
match connect {
SslOptions::Enabled { ca, verify, .. } => {
params.append_pair(
"ssl-mode",
if *verify {
"verify_identity"
} else {
"verify_ca"
},
);
params.append_pair("ssl-ca", ca.as_str());
}
}
let (host, port) = match connect {
DbConnect::Socket(socket) => {
options = options.socket(socket);
params.append_pair("socket", &socket.to_string_lossy());
("localhost", 3306) // ignored when socket is set
}
DbConnect::Tcp { host, port } => {
options = options.host(&host).port(port);
DbConnect::Tcp { host, port } => (host.as_str(), *port),
};
let params = params.finish().replace("%2F", "/");
let params_start = if params.is_empty() { "" } else { "?" };
if port == 3306 {
format!(
"mysql://{}:{}@{}/{}{}{}",
urlencoding::encode(username),
urlencoding::encode(password),
host,
database,
params_start,
params
)
} else {
format!(
"mysql://{}:{}@{}:{}/{}{}{}",
urlencoding::encode(username),
urlencoding::encode(password),
host,
port,
database,
params_start,
params
)
}
}
options.into()
}
Database::Postgres {
database,
username,
@ -214,27 +231,64 @@ impl From<Database> for sqlx::any::AnyConnectOptions {
connect,
ssl_options,
} => {
let mut options = PgConnectOptions::default()
.database(&database)
.username(&username);
if !password.is_empty() {
options = options.password(&password);
let mut params = Serializer::new(String::new());
match ssl_options {
SslOptions::Default => {}
SslOptions::Disabled => {
params.append_pair("sslmode", "disable");
}
if matches!(ssl_options, SslOptions::Disabled) {
options = options.ssl_mode(PgSslMode::Disable);
SslOptions::Enabled { ca, verify, .. } => {
params.append_pair(
"ssl-mode",
if *verify { "verify-full" } else { "verify-ca" },
);
params.append_pair("sslrootcert", ca.as_str());
}
match connect {
}
let (host, port) = match connect {
DbConnect::Socket(socket) => {
options = options.socket(socket);
params.append_pair("host", &socket.to_string_lossy());
("localhost", 5432) // ignored when socket is set
}
DbConnect::Tcp { host, port } => {
options = options.host(&host).port(port);
}
}
options.into()
DbConnect::Tcp { host, port } => (host.as_str(), *port),
};
let params = params.finish().replace("%2F", "/");
let params_start = if params.is_empty() { "" } else { "?" };
if port == 5432 {
format!(
"postgresql://{}:{}@{}/{}{}{}",
urlencoding::encode(username),
urlencoding::encode(password),
host,
database,
params_start,
params
)
} else {
format!(
"postgresql://{}:{}@{}:{}/{}{}{}",
urlencoding::encode(username),
urlencoding::encode(password),
host,
port,
database,
params_start,
params
)
}
}
}
}
}
#[cfg(feature = "db-sqlx")]
impl TryFrom<Database> for sqlx::any::AnyConnectOptions {
type Error = sqlx::Error;
fn try_from(cfg: Database) -> Result<Self, Self::Error> {
use std::str::FromStr;
sqlx::any::AnyConnectOptions::from_str(&cfg.url())
}
}

View file

@ -6,6 +6,10 @@ use nextcloud_config_parser::RedisConfig;
#[cfg(feature = "redis-connect")]
use redis::ConnectionInfo;
#[cfg(feature = "db-sqlx")]
use sqlx::mysql::{MySqlConnectOptions, MySqlSslMode};
#[cfg(feature = "db-sqlx")]
use sqlx::sqlite::SqliteConnectOptions;
#[cfg(feature = "db-sqlx")]
use sqlx::{any::AnyConnectOptions, postgres::PgConnectOptions};
#[cfg(feature = "db-sqlx")]
use std::str::FromStr;
@ -39,14 +43,12 @@ fn test_parse_config_basic() {
},
&config.database,
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from_str(
"mysql://nextcloud:secret@127.0.0.1/nextcloud?ssl-mode=disabled",
)
.unwrap(),
config.database.into(),
assert_eq!(
config.database.url(),
"mysql://nextcloud:secret@127.0.0.1/nextcloud?ssl-mode=disabled"
);
#[cfg(feature = "redis-connect")]
assert_debug_equal(
RedisConfig::Single(ConnectionInfo::from_str("redis://127.0.0.1").unwrap()),
@ -110,14 +112,12 @@ fn test_parse_comment_whitespace() {
},
&config.database,
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from_str(
"mysql://nextcloud:secret@127.0.0.1/nextcloud?ssl-mode=disabled",
)
.unwrap(),
config.database.into(),
assert_eq!(
config.database.url(),
"mysql://nextcloud:secret@127.0.0.1/nextcloud?ssl-mode=disabled"
);
#[cfg(feature = "redis-connect")]
assert_debug_equal(
RedisConfig::Single(ConnectionInfo::from_str("redis://127.0.0.1").unwrap()),
@ -141,13 +141,10 @@ fn test_parse_port_in_host() {
},
&config.database,
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from_str(
"mysql://nextcloud:secret@127.0.0.1:1234/nextcloud?ssl-mode=disabled",
)
.unwrap(),
config.database.into(),
assert_eq!(
config.database.url(),
"mysql://nextcloud:secret@127.0.0.1:1234/nextcloud?ssl-mode=disabled"
);
}
@ -164,16 +161,21 @@ fn test_parse_postgres_socket() {
},
&config.database,
);
assert_eq!(
config.database.url(),
"postgresql://redacted:redacted@localhost/nextcloud?host=/var/run/postgresql"
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from(
PgConnectOptions::new()
.socket("/var/run/postgresql")
.host("localhost")
.username("redacted")
.password("redacted")
.database("nextcloud"),
),
config.database.into(),
PgConnectOptions::from_str(&config.database.url()).unwrap(),
);
}
@ -190,15 +192,19 @@ fn test_parse_postgres_socket_no_pass() {
},
&config.database,
);
assert_eq!(
config.database.url(),
"postgresql://redacted:@localhost/nextcloud?host=/var/run/postgresql"
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from(
PgConnectOptions::new()
.socket("/var/run/postgresql")
.host("localhost")
.username("redacted")
.database("nextcloud"),
),
config.database.into(),
PgConnectOptions::from_str(&config.database.url()).unwrap(),
);
}
@ -215,16 +221,21 @@ fn test_parse_postgres_socket_folder() {
},
&config.database,
);
assert_eq!(
config.database.url(),
"postgresql://redacted:redacted@localhost/nextcloud?host=/var/run/postgresql"
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from(
PgConnectOptions::new()
.socket("/var/run/postgresql")
.host("localhost")
.username("redacted")
.password("redacted")
.database("nextcloud"),
),
config.database.into(),
PgConnectOptions::from_str(&config.database.url()).unwrap(),
);
}
@ -265,14 +276,12 @@ fn test_parse_config_multiple() {
},
&config.database,
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from_str(
"mysql://nextcloud:secret@127.0.0.1/nextcloud?ssl-mode=disabled",
)
.unwrap(),
config.database.into(),
assert_eq!(
config.database.url(),
"mysql://nextcloud:secret@127.0.0.1/nextcloud?ssl-mode=disabled"
);
#[cfg(feature = "redis-connect")]
assert_debug_equal(
RedisConfig::Single(ConnectionInfo::from_str("redis://127.0.0.1").unwrap()),
@ -294,7 +303,7 @@ fn test_parse_config_multiple_no_glob() {
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from_str("sqlite:///nc/nextcloud.db").unwrap(),
config.database.into(),
config.database.try_into().unwrap(),
);
#[cfg(feature = "redis-connect")]
assert_debug_equal(
@ -319,13 +328,21 @@ fn test_parse_config_mysql_fqdn() {
},
&config.database,
);
assert_eq!(
config.database.url(),
"mysql://nextcloud:secret@db.example.com/nextcloud"
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from_str(
"mysql://nextcloud:secret@db.example.com/nextcloud?ssl-mode=preferred",
)
.unwrap(),
config.database.into(),
MySqlConnectOptions::new()
.username("nextcloud")
.password("secret")
.database("nextcloud")
.host("db.example.com")
.ssl_mode(MySqlSslMode::Preferred),
MySqlConnectOptions::from_str(&config.database.url()).unwrap(),
);
}
@ -345,13 +362,21 @@ fn test_parse_config_mysql_ip_no_verify() {
},
&config.database,
);
assert_eq!(
config.database.url(),
"mysql://nextcloud:secret@1.2.3.4/nextcloud"
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from_str(
"mysql://nextcloud:secret@1.2.3.4/nextcloud?ssl-mode=preferred",
)
.unwrap(),
config.database.into(),
MySqlConnectOptions::new()
.username("nextcloud")
.password("secret")
.database("nextcloud")
.host("1.2.3.4")
.ssl_mode(MySqlSslMode::Preferred),
MySqlConnectOptions::from_str(&config.database.url()).unwrap(),
);
}
@ -376,13 +401,22 @@ fn test_parse_config_mysql_ssl_ca() {
},
&config.database,
);
assert_eq!(
config.database.url(),
"mysql://nextcloud:secret@db.example.com/nextcloud?ssl-mode=verify_identity&ssl-ca=/ca-cert.pem"
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from_str(
"mysql://nextcloud:secret@db.example.com/nextcloud?ssl-mode=verify_identity&ssl-ca=/ca-cert.pem",
)
.unwrap(),
config.database.into(),
MySqlConnectOptions::new()
.username("nextcloud")
.password("secret")
.database("nextcloud")
.host("db.example.com")
.ssl_mode(MySqlSslMode::VerifyIdentity)
.ssl_ca("/ca-cert.pem"),
MySqlConnectOptions::from_str(&config.database.url()).unwrap(),
);
}
@ -407,13 +441,21 @@ fn test_parse_config_mysql_ssl_ca_no_verify() {
},
&config.database,
);
assert_eq!(
config.database.url(),
"mysql://nextcloud:secret@db.example.com/nextcloud?ssl-mode=verify_ca&ssl-ca=/ca-cert.pem"
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from_str(
"mysql://nextcloud:secret@db.example.com/nextcloud?ssl-mode=verify_ca&ssl-ca=/ca-cert.pem",
)
.unwrap(),
config.database.into(),
MySqlConnectOptions::new()
.username("nextcloud")
.password("secret")
.database("nextcloud")
.host("db.example.com")
.ssl_mode(MySqlSslMode::VerifyCa)
.ssl_ca("/ca-cert.pem"),
MySqlConnectOptions::from_str(&config.database.url()).unwrap(),
);
}
@ -433,17 +475,21 @@ fn test_parse_postgres_ip() {
},
&config.database,
);
assert_eq!(
config.database.url(),
"postgresql://redacted:redacted@1.2.3.4/nextcloud?sslmode=disable"
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from(
PgConnectOptions::new()
.host("1.2.3.4")
.username("redacted")
.password("redacted")
.database("nextcloud")
.port(5432)
.ssl_mode(sqlx::postgres::PgSslMode::Disable),
),
config.database.into(),
PgConnectOptions::from_str(&config.database.url()).unwrap(),
);
}
@ -463,16 +509,20 @@ fn test_parse_postgres_fqdn() {
},
&config.database,
);
assert_eq!(
config.database.url(),
"postgresql://redacted:redacted@pg.example.com/nextcloud"
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
AnyConnectOptions::from(
PgConnectOptions::new()
.host("pg.example.com")
.username("redacted")
.password("redacted")
.port(5432)
.database("nextcloud"),
),
config.database.into(),
PgConnectOptions::from_str(&config.database.url()).unwrap(),
);
}
@ -485,6 +535,12 @@ fn test_parse_config_sqlite_default_db() {
},
&config.database,
);
#[cfg(feature = "db-sqlx")]
assert_debug_equal(
SqliteConnectOptions::new().filename("/nc/data/owncloud.db"),
SqliteConnectOptions::from_str(&config.database.url()).unwrap(),
);
}
#[test]