Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions crates/cli/src/commands/local_db/manifest_urls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use anyhow::{Context, Result};
use clap::Parser;
use raindex_app_settings::yaml::{
raindex::{RaindexYaml, RaindexYamlValidation},
YamlParsable,
};
use std::io::{self, Write};
use url::Url;

#[derive(Debug, Clone, Parser)]
#[command(about = "Print local DB remote manifest URLs from settings YAML")]
pub struct ManifestUrls {
#[clap(
long,
help = "Full YAML document that configures local DB remotes",
value_name = "YAML"
)]
pub settings_yaml: String,
}

impl ManifestUrls {
pub fn execute(self) -> Result<()> {
let mut stdout = io::stdout();
self.execute_to(&mut stdout)
}

fn execute_to<W: Write>(&self, writer: &mut W) -> Result<()> {
let urls = manifest_urls_from_settings(&self.settings_yaml)?;

for url in urls {
writeln!(writer, "{url}")?;
}

Ok(())
}
}

fn manifest_urls_from_settings(settings_yaml: &str) -> Result<Vec<Url>> {
let raindex_yaml = RaindexYaml::new(
vec![settings_yaml.to_string()],
RaindexYamlValidation {
local_db_remotes: true,
..Default::default()
},
)
.context("failed to parse settings YAML")?;
let remotes = raindex_yaml
.get_local_db_remotes()
.context("failed to parse local-db-remotes from settings YAML")?;

Ok(remotes
.into_iter()
.collect::<std::collections::BTreeMap<_, _>>()
.into_values()
.map(|remote| remote.url)
.collect())
}

#[cfg(test)]
mod tests {
use super::*;

fn render(args: ManifestUrls) -> Result<String> {
let mut buffer = Vec::new();
args.execute_to(&mut buffer)?;
Ok(String::from_utf8(buffer).unwrap())
}

#[test]
fn single_remote_url_prints_one_line() {
let yaml = r#"
version: 6
local-db-remotes:
raindex: https://example.com/manifest.yaml
"#;

let output = render(ManifestUrls {
settings_yaml: yaml.to_string(),
})
.unwrap();

assert_eq!(output, "https://example.com/manifest.yaml\n");
}

#[test]
fn duplicate_remote_urls_fail_settings_parsing() {
let yaml = r#"
version: 6
local-db-remotes:
raindex-a: https://example.com/manifest.yaml
raindex-b: https://example.com/manifest.yaml
"#;

let err = render(ManifestUrls {
settings_yaml: yaml.to_string(),
})
.unwrap_err();
let message = err.to_string();

assert!(message.contains("failed to parse settings YAML"));
}

#[test]
fn no_remotes_prints_nothing() {
let yaml = r#"
version: 6
"#;

let output = render(ManifestUrls {
settings_yaml: yaml.to_string(),
})
.unwrap();

assert_eq!(output, "");
}

#[test]
fn default_output_is_only_one_url_per_line() {
let yaml = r#"
version: 6
local-db-remotes:
raindex-a: https://example.com/a.yaml
raindex-b: https://example.com/b.yaml
"#;

let output = render(ManifestUrls {
settings_yaml: yaml.to_string(),
})
.unwrap();

assert_eq!(
output,
"https://example.com/a.yaml\nhttps://example.com/b.yaml\n"
);
}

#[test]
fn missing_version_fails_before_printing_urls() {
let yaml = r#"
local-db-remotes:
raindex: https://example.com/manifest.yaml
"#;

let err = render(ManifestUrls {
settings_yaml: yaml.to_string(),
})
.unwrap_err();
let message = err.to_string();

assert!(message.contains("failed to parse settings YAML"));
}
}
6 changes: 6 additions & 0 deletions crates/cli/src/commands/local_db/mod.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
pub mod cli;
pub mod executor;
pub mod manifest_urls;
pub mod pipeline;

use anyhow::Result;
use clap::Subcommand;
use cli::RunPipeline;
use manifest_urls::ManifestUrls;

#[derive(Subcommand)]
#[command(about = "Local database operations")]
pub enum LocalDbCommands {
#[command(name = "sync")]
Sync(RunPipeline),

#[command(name = "manifest-urls")]
ManifestUrls(ManifestUrls),
}

impl LocalDbCommands {
pub async fn execute(self) -> Result<()> {
match self {
LocalDbCommands::Sync(cmd) => cmd.execute().await,
LocalDbCommands::ManifestUrls(cmd) => cmd.execute(),
}
}
}
46 changes: 40 additions & 6 deletions crates/settings/src/local_db_remotes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ impl YamlParsableHash for LocalDbRemoteCfg {
_: Option<&Context>,
) -> Result<HashMap<String, Self>, YamlError> {
let mut remotes = HashMap::new();
let mut remote_keys_by_url = HashMap::new();

for document in documents {
let document_read = document.read().map_err(|_| YamlError::ReadLockError)?;
Expand Down Expand Up @@ -65,18 +66,29 @@ impl YamlParsableHash for LocalDbRemoteCfg {
location: location.clone(),
})?;

let remote = LocalDbRemoteCfg {
document: document.clone(),
key: remote_key.clone(),
url,
};

if remotes.contains_key(&remote_key) {
return Err(YamlError::KeyShadowing(
remote_key,
"local-db-remotes".to_string(),
));
}
if let Some(existing_key) = remote_keys_by_url.get(&url) {
return Err(YamlError::Field {
kind: FieldErrorKind::InvalidValue {
field: "url".to_string(),
reason: format!("duplicates local-db-remotes[{}]", existing_key),
},
location,
});
}

let remote = LocalDbRemoteCfg {
document: document.clone(),
key: remote_key.clone(),
url,
};

remote_keys_by_url.insert(remote.url.clone(), remote.key.clone());
remotes.insert(remote.key.clone(), remote);
}
}
Expand Down Expand Up @@ -156,6 +168,28 @@ local-db-remotes:
);
}

#[test]
fn test_parse_local_db_remotes_from_yaml_duplicate_url() {
let yaml = r#"
local-db-remotes:
mainnet: https://example.com/localdb/mainnet
duplicate: https://example.com/localdb/mainnet
"#;
let err =
LocalDbRemoteCfg::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err();

assert_eq!(
err,
YamlError::Field {
kind: FieldErrorKind::InvalidValue {
field: "url".to_string(),
reason: "duplicates local-db-remotes[mainnet]".to_string(),
},
location: "local-db-remotes[duplicate]".to_string(),
}
);
}

#[test]
fn test_parse_local_db_remotes_optional_absent_is_ok() {
// No local-db-remotes key
Expand Down
Loading