Skip to content

Commit db050c0

Browse files
authored
Tweak the unix_path interface (#407)
We need a small tweak to the unix_path interface so that we can load the associated port from the instance credentials. UnixPath::with_port_suffix(...)
1 parent bbd2247 commit db050c0

File tree

8 files changed

+165
-162
lines changed

8 files changed

+165
-162
lines changed

gel-dsn/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "gel-dsn"
33
license = "MIT/Apache-2.0"
4-
version = "0.1.1"
4+
version = "0.2.0"
55
authors = ["MagicStack Inc. <[email protected]>"]
66
edition = "2021"
77
description = "Data-source name (DSN) parser for Gel and PostgreSQL databases."

gel-dsn/src/gel/config.rs

+65-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use super::{error::*, BuildContextImpl, FromParamStr, InstanceName, Param, Params};
22
use crate::{
33
gel::parse_duration,
4-
host::{Host, HostTarget, HostType},
4+
host::{Host, HostType},
55
};
66
use rustls_pki_types::CertificateDer;
77
use serde::{Deserialize, Serialize};
88
use std::{
9+
borrow::Cow,
910
collections::HashMap,
1011
fmt,
1112
path::{Path, PathBuf},
@@ -102,7 +103,7 @@ pub struct Config {
102103
impl Default for Config {
103104
fn default() -> Self {
104105
Self {
105-
host: Host::new(DEFAULT_HOST.clone(), DEFAULT_PORT, HostTarget::Gel),
106+
host: Host::new(DEFAULT_HOST.clone(), DEFAULT_PORT),
106107
db: DatabaseBranch::Default,
107108
user: DEFAULT_USER.to_string(),
108109
instance_name: None,
@@ -194,17 +195,6 @@ impl Config {
194195
}
195196
}
196197

197-
pub fn with_unix_path(&self, path: &Path) -> Self {
198-
Self {
199-
host: Host::new(
200-
HostType::from_unix_path(PathBuf::from(path)),
201-
DEFAULT_PORT,
202-
HostTarget::Raw,
203-
),
204-
..self.clone()
205-
}
206-
}
207-
208198
pub fn with_branch(&self, branch: &str) -> Self {
209199
Self {
210200
db: DatabaseBranch::Branch(branch.to_string()),
@@ -600,6 +590,68 @@ impl FromStr for TcpKeepalive {
600590
}
601591
}
602592

593+
#[derive(derive_more::Debug, Clone, PartialEq, Eq)]
594+
enum UnixPathInner {
595+
/// The selected port will be appended to the path.
596+
#[debug("{:?}{{port}}", _0)]
597+
PortSuffixed(PathBuf),
598+
/// The path will be used as-is.
599+
#[debug("{:?}", _0)]
600+
Exact(PathBuf),
601+
}
602+
603+
#[derive(Clone, PartialEq, Eq, derive_more::Debug)]
604+
pub struct UnixPath {
605+
#[debug("{:?}", inner)]
606+
inner: UnixPathInner,
607+
}
608+
609+
impl UnixPath {
610+
/// The selected port will be appended to the path.
611+
pub fn with_port_suffix(path: PathBuf) -> Self {
612+
UnixPath {
613+
inner: UnixPathInner::PortSuffixed(path),
614+
}
615+
}
616+
617+
/// The path will be used as-is.
618+
pub fn exact(path: PathBuf) -> Self {
619+
UnixPath {
620+
inner: UnixPathInner::Exact(path),
621+
}
622+
}
623+
624+
/// Returns a path with the port suffix appended.
625+
pub fn path_with_port(&self, port: u16) -> Cow<Path> {
626+
match &self.inner {
627+
UnixPathInner::PortSuffixed(path) => {
628+
let Some(filename) = path.file_name() else {
629+
return Cow::Owned(path.join(port.to_string()));
630+
};
631+
let mut path = path.clone();
632+
let mut filename = filename.to_owned();
633+
filename.push(&port.to_string());
634+
path.set_file_name(filename);
635+
Cow::Owned(path)
636+
}
637+
UnixPathInner::Exact(path) => Cow::Borrowed(path),
638+
}
639+
}
640+
}
641+
642+
impl FromStr for UnixPath {
643+
type Err = ParseError;
644+
fn from_str(s: &str) -> Result<Self, Self::Err> {
645+
Ok(UnixPath::exact(PathBuf::from(s)))
646+
}
647+
}
648+
649+
impl<T: Into<PathBuf>> From<T> for UnixPath {
650+
fn from(path: T) -> Self {
651+
UnixPath::exact(path.into())
652+
}
653+
}
654+
603655
/// Classic-style connection options.
604656
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
605657
#[serde(default)]

gel-dsn/src/gel/mod.rs

+16-14
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ mod tests {
357357
use serde_json::json;
358358

359359
use super::*;
360-
use crate::host::{Host, HostTarget, HostType};
360+
use crate::host::{Host, HostType};
361361
use std::{collections::HashMap, time::Duration};
362362

363363
#[test]
@@ -370,11 +370,7 @@ mod tests {
370370
assert_eq!(
371371
cfg.unwrap(),
372372
Config {
373-
host: Host::new(
374-
HostType::try_from_str("hostname").unwrap(),
375-
1234,
376-
HostTarget::Gel
377-
),
373+
host: Host::new(HostType::try_from_str("hostname").unwrap(), 1234,),
378374
..Default::default()
379375
}
380376
);
@@ -398,10 +394,7 @@ mod tests {
398394
.build()
399395
.expect("Failed to build credentials");
400396

401-
assert_eq!(
402-
credentials.host,
403-
Host::new(DEFAULT_HOST.clone(), 10702, HostTarget::Gel)
404-
);
397+
assert_eq!(credentials.host, Host::new(DEFAULT_HOST.clone(), 10702));
405398
assert_eq!(&credentials.user, "test3n");
406399
assert_eq!(
407400
credentials.db,
@@ -441,12 +434,21 @@ mod tests {
441434

442435
// Test unix path with a port
443436
let cfg = Builder::new()
444-
.port(8888_u16)
437+
.port(8888)
445438
.unix_path("/test")
446439
.build()
447440
.unwrap();
448441
let host = cfg.host.target_name().unwrap();
449-
assert_eq!(host.path(), Some(Path::new("/test/.s.EDGEDB.admin.8888")));
442+
assert_eq!(host.path(), Some(Path::new("/test")));
443+
444+
// Test unix path with a port
445+
let cfg = Builder::new()
446+
.port(8888)
447+
.unix_path(UnixPath::with_port_suffix(PathBuf::from("/prefix.")))
448+
.build()
449+
.unwrap();
450+
let host = cfg.host.target_name().unwrap();
451+
assert_eq!(host.path(), Some(Path::new("/prefix.8888")));
450452
}
451453

452454
/// Test that the hidden CloudCerts env var is parsed correctly.
@@ -455,7 +457,7 @@ mod tests {
455457
let cloud_cert =
456458
HashMap::from_iter([("_GEL_CLOUD_CERTS".to_string(), "local".to_string())]);
457459
let cfg = Builder::new()
458-
.port(5656_u16)
460+
.port(5656)
459461
.without_system()
460462
.with_env_impl(cloud_cert)
461463
.build()
@@ -466,7 +468,7 @@ mod tests {
466468
#[test]
467469
fn test_tcp_keepalive() {
468470
let cfg = Builder::new()
469-
.port(5656_u16)
471+
.port(5656)
470472
.tcp_keepalive(TcpKeepalive::Explicit(Duration::from_secs(10)))
471473
.without_system()
472474
.build()

gel-dsn/src/gel/param.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use url::Url;
88

99
use super::{
1010
duration, error::*, BuildContext, ClientSecurity, CloudCerts, CloudCredentialsFile,
11-
CredentialsFile, InstanceName, TcpKeepalive, TlsSecurity,
11+
CredentialsFile, InstanceName, TcpKeepalive, TlsSecurity, UnixPath,
1212
};
1313
use crate::{gel::context_trace, host::HostType, EnvVar, FileAccess};
1414

@@ -43,7 +43,8 @@ impl_from_param_str!(
4343
ClientSecurity,
4444
CloudCredentialsFile,
4545
CloudCerts,
46-
TcpKeepalive
46+
TcpKeepalive,
47+
UnixPath
4748
);
4849

4950
impl FromParamStr for std::time::Duration {

gel-dsn/src/gel/params.rs

+52-67
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ use super::{
1616
error::*,
1717
project::{find_project_file, ProjectDir},
1818
BuildContext, BuildContextImpl, ClientSecurity, CloudCerts, Config, DatabaseBranch,
19-
FromParamStr, InstanceName, Logging, Param, ParamSource, TcpKeepalive, TlsSecurity,
19+
FromParamStr, InstanceName, Logging, Param, ParamSource, TcpKeepalive, TlsSecurity, UnixPath,
2020
DEFAULT_CONNECT_TIMEOUT, DEFAULT_PORT, DEFAULT_WAIT,
2121
};
2222
use crate::{
2323
env::SystemEnvVars,
2424
file::SystemFileAccess,
2525
gel::{context_trace, Authentication},
26-
host::{Host, HostTarget, HostType},
26+
host::{Host, HostType},
2727
user::SystemUserProfile,
2828
EnvVar, FileAccess, UserProfile,
2929
};
@@ -233,12 +233,7 @@ define_params!(
233233
/// The port.
234234
port: u16,
235235
/// The unix socket path.
236-
///
237-
/// If set, the client will connect to the database via a Unix socket.
238-
/// If a port is also set, this must refer to a directory in the filesystem
239-
/// containing a Gel admin socket. If no port is set, the client will
240-
/// connect to the database via a Unix socket on the exact given path.
241-
unix_path: PathBuf,
236+
unix_path: UnixPath,
242237
/// The database name. Used for EdgeDB < 5. For Gel or EdgeDB >= 5, use
243238
/// [`Builder::branch`].
244239
database: String,
@@ -788,42 +783,38 @@ impl Params {
788783

789784
let instance = instance?;
790785
if let Some(instance) = &instance {
791-
// Special case: if the unix path is set we ignore the instance
792-
// credentials.
793-
if explicit.unix_path.is_none() {
794-
match &instance {
795-
InstanceName::Local(local) => {
796-
let instance = parse_instance(local, context)?;
797-
context_trace!(context, "Instance: {:?}", instance);
798-
explicit.merge(instance);
799-
}
800-
InstanceName::Cloud { .. } => {
801-
let profile = explicit
802-
.cloud_profile
803-
.get(context)?
804-
.unwrap_or("default".to_string());
805-
let cloud = parse_cloud(&profile, context)?;
806-
context_trace!(context, "Cloud: {:?}", cloud);
807-
explicit.merge(cloud);
808-
809-
if let Some(secret_key) = explicit.secret_key.get(context)? {
810-
match instance.cloud_address(&secret_key) {
811-
Ok(Some(address)) => {
812-
explicit.host = Param::Unparsed(address);
813-
}
814-
Ok(None) => {
815-
unreachable!();
816-
}
817-
Err(e) => {
818-
// Special case: we ignore the secret key error until the final phase
819-
if phase == BuildPhase::Project {
820-
return Err(e);
821-
}
786+
match &instance {
787+
InstanceName::Local(local) => {
788+
let instance = parse_instance(local, context)?;
789+
context_trace!(context, "Instance: {:?}", instance);
790+
explicit.merge(instance);
791+
}
792+
InstanceName::Cloud { .. } => {
793+
let profile = explicit
794+
.cloud_profile
795+
.get(context)?
796+
.unwrap_or("default".to_string());
797+
let cloud = parse_cloud(&profile, context)?;
798+
context_trace!(context, "Cloud: {:?}", cloud);
799+
explicit.merge(cloud);
800+
801+
if let Some(secret_key) = explicit.secret_key.get(context)? {
802+
match instance.cloud_address(&secret_key) {
803+
Ok(Some(address)) => {
804+
explicit.host = Param::Unparsed(address);
805+
}
806+
Ok(None) => {
807+
unreachable!();
808+
}
809+
Err(e) => {
810+
// Special case: we ignore the secret key error until the final phase
811+
if phase == BuildPhase::Project {
812+
return Err(e);
822813
}
823814
}
824-
} else {
825-
return Err(ParseError::SecretKeyNotFound);
826815
}
816+
} else {
817+
return Err(ParseError::SecretKeyNotFound);
827818
}
828819
}
829820
}
@@ -859,27 +850,15 @@ impl Params {
859850
}
860851

861852
let host = if let Some(unix_path) = computed.unix_path {
862-
match port {
863-
Some(port) => Host::new(
864-
HostType::from_unix_path(unix_path),
865-
port,
866-
HostTarget::GelAdmin,
867-
),
868-
None => Host::new(
869-
HostType::from_unix_path(unix_path),
870-
DEFAULT_PORT,
871-
HostTarget::Raw,
872-
),
873-
}
853+
let path = unix_path
854+
.path_with_port(port.unwrap_or(DEFAULT_PORT))
855+
.into_owned();
856+
Host::new(HostType::from_unix_path(path), DEFAULT_PORT)
874857
} else {
875858
let host = match (computed.host, port) {
876-
(Some(host), Some(port)) => Host::new(host, port, HostTarget::Gel),
877-
(Some(host), None) => Host::new(host, DEFAULT_PORT, HostTarget::Gel),
878-
(None, Some(port)) => Host::new(
879-
HostType::try_from_str("localhost").unwrap(),
880-
port,
881-
HostTarget::Gel,
882-
),
859+
(Some(host), Some(port)) => Host::new(host, port),
860+
(Some(host), None) => Host::new(host, DEFAULT_PORT),
861+
(None, Some(port)) => Host::new(HostType::try_from_str("localhost").unwrap(), port),
883862
(None, None) => {
884863
return Ok(None);
885864
}
@@ -1524,26 +1503,32 @@ mod tests {
15241503
eprintln!("{:?}", params);
15251504

15261505
let params = Builder::default()
1527-
.unix_path(Path::new("/"))
1506+
.unix_path(UnixPath::with_port_suffix(PathBuf::from(
1507+
"/.s.EDGEDB.admin.",
1508+
)))
15281509
.port(1234)
15291510
.without_system()
15301511
.build()
15311512
.expect("Unix path and port is OK");
15321513
assert_eq!(params.host.to_string(), "/.s.EDGEDB.admin.1234");
15331514
eprintln!("{:?}", params);
15341515

1535-
// This is allowed, but instance credentials are ignored in this case
1536-
// and just transferred to the output Config.
1516+
// Pull the port from the credentials.
15371517
let params = Builder::default()
1538-
.instance(InstanceName::Local("instancedoesnotexist".to_string()))
1539-
.unix_path(Path::new("/"))
1518+
.instance(InstanceName::Local("instancename".to_string()))
1519+
.unix_path(UnixPath::with_port_suffix(PathBuf::from("/tmp/port.")))
15401520
.without_system()
1521+
.with_fs_impl(HashMap::from_iter([(
1522+
PathBuf::from("/home/edgedb/.config/edgedb/credentials/instancename.json"),
1523+
r#"{ "user": "user", "port": 12345 }"#.to_string(),
1524+
)]))
1525+
.with_user_impl("edgedb")
15411526
.build()
15421527
.expect("Unix path and instance is OK");
1543-
assert_eq!(params.host.to_string(), "/");
1528+
assert_eq!(params.host.to_string(), "/tmp/port.12345");
15441529
assert_eq!(
15451530
params.instance_name,
1546-
Some(InstanceName::Local("instancedoesnotexist".to_string()))
1531+
Some(InstanceName::Local("instancename".to_string()))
15471532
);
15481533
eprintln!("{:?}", params);
15491534
}

0 commit comments

Comments
 (0)