1use std::path::PathBuf;
3
4use anyhow::{Context, Result};
5use async_nats::{Client, ConnectOptions};
6
7const DEFAULT_NATS_ADDR: &str = "nats://127.0.0.1:4222";
8
9pub async fn get_client(
11 url: Option<String>,
12 seed: Option<String>,
13 jwt: Option<String>,
14 creds_path: Option<PathBuf>,
15 ca_path: Option<PathBuf>,
16) -> Result<Client> {
17 let mut opts = ConnectOptions::new();
18 opts = match (seed, jwt, creds_path) {
19 (Some(seed), Some(jwt), None) => {
20 let jwt = resolve_jwt(jwt).await?;
21 let kp = std::sync::Arc::new(get_seed(seed).await?);
22
23 opts.jwt(jwt, move |nonce| {
24 let key_pair = kp.clone();
25 async move { key_pair.sign(&nonce).map_err(async_nats::AuthError::new) }
26 })
27 }
28 (None, None, Some(creds)) => opts.credentials_file(creds).await?,
29 (None, None, None) => opts,
30 _ => {
31 return Err(anyhow::anyhow!(
33 "Got incorrect combination of connection options. Should either have nothing set, a seed, a jwt, or a credentials file"
34 ));
35 }
36 };
37 if let Some(ca) = ca_path {
38 opts = opts.add_root_certificates(ca).require_tls(true);
39 }
40 opts.connect(url.unwrap_or_else(|| DEFAULT_NATS_ADDR.to_string()))
41 .await
42 .map_err(Into::into)
43}
44
45async fn get_seed(seed: String) -> Result<nkeys::KeyPair> {
47 let raw_seed = if seed.len() == 58 && seed.starts_with('S') {
49 seed
50 } else {
51 tokio::fs::read_to_string(seed)
52 .await
53 .context("Unable to read seed file")?
54 };
55
56 nkeys::KeyPair::from_seed(&raw_seed).map_err(anyhow::Error::from)
57}
58
59async fn resolve_jwt(jwt_or_file: String) -> Result<String> {
62 if tokio::fs::metadata(&jwt_or_file)
63 .await
64 .map(|metadata| metadata.is_file())
65 .unwrap_or(false)
66 {
67 tokio::fs::read_to_string(jwt_or_file)
68 .await
69 .map_err(|e| anyhow::anyhow!("Error loading JWT from file: {e}"))
70 } else {
71 Ok(jwt_or_file)
74 }
75}