Skip to content

Commit 2177ea5

Browse files
authored
feat(trogon-gateway): use shared runtime config crate (#116)
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent 9331e01 commit 2177ea5

File tree

8 files changed

+272
-80
lines changed

8 files changed

+272
-80
lines changed

rsworkspace/Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rsworkspace/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ all = "deny"
1414
acp-nats = { path = "crates/acp-nats" }
1515
acp-telemetry = { path = "crates/acp-telemetry" }
1616
trogon-nats = { path = "crates/trogon-nats" }
17+
trogon-service-config = { path = "crates/trogon-service-config" }
1718
trogon-source-discord = { path = "crates/trogon-source-discord" }
1819
trogon-source-github = { path = "crates/trogon-source-github" }
1920
trogon-source-gitlab = { path = "crates/trogon-source-gitlab" }

rsworkspace/crates/trogon-gateway/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ serde = { workspace = true }
2020
tokio = { workspace = true, features = ["full"] }
2121
tracing = { workspace = true }
2222
trogon-nats = { workspace = true }
23+
trogon-service-config = { workspace = true }
2324
trogon-source-discord = { workspace = true }
2425
trogon-source-github = { workspace = true }
2526
trogon-source-gitlab = { workspace = true }
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
use std::path::PathBuf;
1+
use trogon_service_config::RuntimeConfigArgs;
22

33
#[derive(clap::Parser, Clone)]
44
#[command(name = "trogon-gateway", about = "Unified gateway ingestion binary")]
55
pub struct Cli {
6+
#[command(flatten)]
7+
pub runtime: RuntimeConfigArgs,
8+
69
#[command(subcommand)]
710
pub command: Command,
811
}
912

1013
#[derive(clap::Subcommand, Clone)]
1114
pub enum Command {
12-
Serve {
13-
#[arg(long, short)]
14-
config: Option<PathBuf>,
15-
},
15+
Serve,
1616
}

rsworkspace/crates/trogon-gateway/src/config.rs

Lines changed: 54 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ use std::fmt;
22
use std::path::Path;
33

44
use confique::Config;
5+
#[cfg(test)]
6+
use trogon_nats::NatsAuth;
57
use trogon_nats::jetstream::StreamMaxAge;
6-
use trogon_nats::{NatsAuth, NatsToken, SubjectTokenViolation};
8+
use trogon_nats::{NatsToken, SubjectTokenViolation};
9+
use trogon_service_config::{NatsArgs, NatsConfigSection, load_config, resolve_nats};
710
use trogon_source_discord::config::DiscordBotToken;
811
use trogon_source_github::config::GitHubWebhookSecret;
912
use trogon_source_gitlab::config::GitLabWebhookSecret;
@@ -164,7 +167,7 @@ struct GatewayConfig {
164167
#[config(nested)]
165168
http_server: HttpServerConfig,
166169
#[config(nested)]
167-
nats: NatsConfig,
170+
nats: NatsConfigSection,
168171
#[config(nested)]
169172
sources: SourcesConfig,
170173
}
@@ -175,22 +178,6 @@ struct HttpServerConfig {
175178
port: u16,
176179
}
177180

178-
#[derive(Config)]
179-
struct NatsConfig {
180-
#[config(env = "NATS_URL", default = "localhost:4222")]
181-
url: String,
182-
#[config(env = "NATS_CREDS")]
183-
creds: Option<String>,
184-
#[config(env = "NATS_NKEY")]
185-
nkey: Option<String>,
186-
#[config(env = "NATS_USER")]
187-
user: Option<String>,
188-
#[config(env = "NATS_PASSWORD")]
189-
password: Option<String>,
190-
#[config(env = "NATS_TOKEN")]
191-
token: Option<String>,
192-
}
193-
194181
#[derive(Config)]
195182
struct SourcesConfig {
196183
#[config(nested)]
@@ -358,17 +345,21 @@ impl ResolvedConfig {
358345
}
359346
}
360347

348+
#[cfg(test)]
361349
pub fn load(config_path: Option<&Path>) -> Result<ResolvedConfig, ConfigError> {
362-
let mut builder = GatewayConfig::builder();
363-
if let Some(path) = config_path {
364-
builder = builder.file(path);
365-
}
366-
let cfg = builder.env().load().map_err(ConfigError::Load)?;
367-
resolve(cfg)
350+
load_with_overrides(config_path, &NatsArgs::default())
351+
}
352+
353+
pub fn load_with_overrides(
354+
config_path: Option<&Path>,
355+
nats_overrides: &NatsArgs,
356+
) -> Result<ResolvedConfig, ConfigError> {
357+
let cfg = load_config::<GatewayConfig>(config_path).map_err(ConfigError::Load)?;
358+
resolve(cfg, nats_overrides)
368359
}
369360

370-
fn resolve(cfg: GatewayConfig) -> Result<ResolvedConfig, ConfigError> {
371-
let nats = resolve_nats(&cfg.nats);
361+
fn resolve(cfg: GatewayConfig, nats_overrides: &NatsArgs) -> Result<ResolvedConfig, ConfigError> {
362+
let nats = resolve_nats(&cfg.nats, nats_overrides);
372363
let mut errors = Vec::new();
373364

374365
let github = resolve_github(cfg.sources.github, &mut errors);
@@ -398,38 +389,6 @@ fn resolve(cfg: GatewayConfig) -> Result<ResolvedConfig, ConfigError> {
398389
})
399390
}
400391

401-
fn non_empty(opt: &Option<String>) -> Option<&String> {
402-
opt.as_ref().filter(|s| !s.is_empty())
403-
}
404-
405-
fn resolve_nats(section: &NatsConfig) -> trogon_nats::NatsConfig {
406-
let auth = if let Some(creds) = non_empty(&section.creds) {
407-
NatsAuth::Credentials(creds.clone().into())
408-
} else if let Some(nkey) = non_empty(&section.nkey) {
409-
NatsAuth::NKey(nkey.clone())
410-
} else if let (Some(user), Some(password)) =
411-
(non_empty(&section.user), non_empty(&section.password))
412-
{
413-
NatsAuth::UserPassword {
414-
user: user.clone(),
415-
password: password.clone(),
416-
}
417-
} else if let Some(token) = non_empty(&section.token) {
418-
NatsAuth::Token(token.clone())
419-
} else {
420-
NatsAuth::None
421-
};
422-
423-
let servers: Vec<String> = section
424-
.url
425-
.split(',')
426-
.map(|s| s.trim().to_string())
427-
.filter(|s| !s.is_empty())
428-
.collect();
429-
430-
trogon_nats::NatsConfig::new(servers, auth)
431-
}
432-
433392
fn resolve_github(
434393
section: GithubConfig,
435394
errors: &mut Vec<ConfigValidationError>,
@@ -1721,21 +1680,47 @@ token = "mytoken"
17211680
}
17221681

17231682
#[test]
1724-
fn non_empty_filters_none() {
1725-
let val: Option<String> = None;
1726-
assert!(non_empty(&val).is_none());
1727-
}
1683+
fn load_with_overrides_prefers_cli_nats_values() {
1684+
let toml = r#"
1685+
[nats]
1686+
url = "file1:4222,file2:4222"
1687+
token = "file-token"
1688+
"#;
1689+
let f = write_toml(toml);
1690+
let cfg = load_with_overrides(
1691+
Some(f.path()),
1692+
&NatsArgs {
1693+
nats_url: Some("override:4222".to_string()),
1694+
nats_token: Some("override-token".to_string()),
1695+
..Default::default()
1696+
},
1697+
)
1698+
.expect("load failed");
17281699

1729-
#[test]
1730-
fn non_empty_filters_empty_string() {
1731-
let val = Some(String::new());
1732-
assert!(non_empty(&val).is_none());
1700+
assert_eq!(cfg.nats.servers, vec!["override:4222"]);
1701+
assert!(matches!(cfg.nats.auth, NatsAuth::Token(ref token) if token == "override-token"));
17331702
}
17341703

17351704
#[test]
1736-
fn non_empty_passes_through_nonempty() {
1737-
let val = Some("hello".to_string());
1738-
assert_eq!(non_empty(&val), Some(&"hello".to_string()));
1705+
fn load_with_overrides_keeps_auth_priority() {
1706+
let toml = r#"
1707+
[nats]
1708+
token = "file-token"
1709+
"#;
1710+
let f = write_toml(toml);
1711+
let cfg = load_with_overrides(
1712+
Some(f.path()),
1713+
&NatsArgs {
1714+
nats_creds: Some("/path/to/override.creds".to_string()),
1715+
nats_token: Some("override-token".to_string()),
1716+
..Default::default()
1717+
},
1718+
)
1719+
.expect("load failed");
1720+
1721+
assert!(
1722+
matches!(cfg.nats.auth, NatsAuth::Credentials(ref path) if path == std::path::Path::new("/path/to/override.creds"))
1723+
);
17391724
}
17401725

17411726
#[test]

rsworkspace/crates/trogon-gateway/src/main.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,7 @@ type SourceResult = (&'static str, Result<(), String>);
4141
#[tokio::main]
4242
async fn main() -> Result<(), Box<dyn std::error::Error>> {
4343
let cli = CliArgs::<cli::Cli>::new().parse_args();
44-
45-
let config_path = match cli.command {
46-
cli::Command::Serve { ref config } => config.as_deref(),
47-
};
48-
49-
let resolved = config::load(config_path)?;
44+
let resolved = config::load_with_overrides(cli.runtime.config.as_deref(), &cli.runtime.nats)?;
5045

5146
if !resolved.has_any_source() {
5247
return Err("no sources configured — provide a config file or set source env vars".into());
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "trogon-service-config"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[lints]
7+
workspace = true
8+
9+
[dependencies]
10+
clap = { workspace = true, features = ["derive"] }
11+
confique = { workspace = true }
12+
trogon-nats = { workspace = true }
13+
14+
[dev-dependencies]
15+
tempfile = { workspace = true }

0 commit comments

Comments
 (0)