diff --git a/Cargo.lock b/Cargo.lock index 0b6a14b8..0f0b6796 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4217,6 +4217,18 @@ dependencies = [ "tabled", ] +[[package]] +name = "jsonrpc" +version = "0.1.0" +source = "git+https://github.com/mystenlabs/sui?rev=0f91f6b#0f91f6beabaa0520d1f4bf62977ed59dd1cdd3a8" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "jsonrpsee" version = "0.24.9" @@ -5458,6 +5470,7 @@ dependencies = [ "expect-test", "futures", "insta", + "jsonrpc", "mvr-types", "regex", "reqwest", diff --git a/crates/mvr-cli/Cargo.toml b/crates/mvr-cli/Cargo.toml index 52e791b8..a186d046 100644 --- a/crates/mvr-cli/Cargo.toml +++ b/crates/mvr-cli/Cargo.toml @@ -19,7 +19,8 @@ name = "unit_tests" path = "tests/unit_tests.rs" [dependencies] -bin-version = { git = "https://github.com/mystenlabs/sui", package = "bin-version", rev = "0f91f6b" } +bin-version = { git = "https://github.com/mystenlabs/sui", rev = "0f91f6b" } +jsonrpc = { git = "https://github.com/mystenlabs/sui", rev = "0f91f6b" } clap = { workspace = true, features = ["derive"] } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } diff --git a/crates/mvr-cli/src/commands.rs b/crates/mvr-cli/src/commands.rs index c4e04c0d..2b6f67fe 100644 --- a/crates/mvr-cli/src/commands.rs +++ b/crates/mvr-cli/src/commands.rs @@ -75,8 +75,7 @@ impl Display for CommandOutput { let description = pkg .metadata .get("description") - .map(|s| s.as_str()) - .flatten() + .and_then(|s| s.as_str()) .map(|s| s.to_string()) .unwrap_or("--".italic().to_string()); @@ -110,7 +109,6 @@ impl Display for CommandOutput { "\n{}", "There are multiple pages of results. Use the cursor to paginate through the results." .italic() - .to_string() )?; writeln!( f, @@ -118,7 +116,6 @@ impl Display for CommandOutput { format!("mvr search --cursor {}", next_cursor) .italic() .blue() - .to_string() )?; } diff --git a/crates/mvr-cli/src/lib.rs b/crates/mvr-cli/src/lib.rs index e716c292..f16b5ff9 100644 --- a/crates/mvr-cli/src/lib.rs +++ b/crates/mvr-cli/src/lib.rs @@ -4,6 +4,9 @@ pub mod errors; pub mod types; pub mod utils; +use crate::types::api_data::{ + query_multiple_dependencies, query_package, resolve_name, search_names, +}; use crate::types::MoveTomlPublishedID; use commands::CommandOutput; @@ -12,9 +15,6 @@ use mvr_types::name::VersionedName; use types::api_types::PackageRequest; use types::api_types::SafeGitInfo; use types::Network; -use utils::api_data::resolve_name; -use utils::api_data::search_names; -use utils::api_data::{query_multiple_dependencies, query_package}; use utils::git::shallow_clone_repo; use sui_sdk_types::ObjectId; @@ -408,12 +408,7 @@ fn original_published_id(move_toml_content: &str, target_chain_id: &str) -> Opti .as_table()? .iter() .filter_map(|(_, value)| value.as_table()) - .find(|table| { - table - .get("chain-id") - .and_then(|v| v.as_str()) - .map_or(false, |id| id == target_chain_id) - }); + .find(|table| table.get("chain-id").and_then(|v| v.as_str()) == Some(target_chain_id)); let original_published_id = table.and_then(|table| { table .get("original-published-id") @@ -540,9 +535,9 @@ fn insert_root_dependency( new_package.insert(LOCK_PACKAGE_ID_KEY, value(root_name)); let mut source = Table::new(); - source.insert("git", value(&git_info.repository_url.clone())); - source.insert("rev", value(&git_info.tag.clone())); - source.insert("subdir", value(&git_info.path.clone())); + source.insert("git", value(git_info.repository_url.clone())); + source.insert("rev", value(git_info.tag.clone())); + source.insert("subdir", value(git_info.path.clone())); new_package.insert("source", value(source.into_inline_table())); if let Some(deps) = original_deps { @@ -560,7 +555,7 @@ fn insert_root_dependency( .ok_or_else(|| anyhow!("Failed to get or create package array in lock file".red()))?; for package in packages.iter_mut() { - if let Some(source) = convert_local_dep_to_git(package, &git_info)? { + if let Some(source) = convert_local_dep_to_git(package, git_info)? { package.insert("source", value(source)); } } @@ -601,8 +596,8 @@ fn convert_local_dep_to_git( .and_then(|items| items.get("local")) .map(|local| { let mut new_source = Table::new(); - new_source.insert("git", value(&git_info.repository_url.clone())); - new_source.insert("rev", value(&git_info.tag.clone())); + new_source.insert("git", value(git_info.repository_url.clone())); + new_source.insert("rev", value(git_info.tag.clone())); let local_str = local.as_str().ok_or_else(|| { anyhow!("Failed to get local dependency path. Found empty path on transitive dependency: {}", local) @@ -686,7 +681,7 @@ async fn update_mvr_packages( .red() ); }; - move_toml.add_dependency(&name, &package_name)?; + move_toml.add_dependency(&name, package_name)?; move_toml.save_to_file()?; diff --git a/crates/mvr-cli/src/main.rs b/crates/mvr-cli/src/main.rs index 86b6a925..9120e098 100644 --- a/crates/mvr-cli/src/main.rs +++ b/crates/mvr-cli/src/main.rs @@ -1,3 +1,7 @@ +use mvr::types::resolver_alt::new_package_resolver; + +use std::env; + use anyhow::Result; use clap::Parser; use mvr::utils::sui_binary::check_sui_version; @@ -11,6 +15,9 @@ struct Cli { #[arg(long)] resolve_move_dependencies: Option, + #[arg(long, global = true)] + resolve_deps: bool, + #[command(subcommand)] command: Option, @@ -23,10 +30,17 @@ struct Cli { async fn main() -> Result<()> { let cli = Cli::parse(); + // If we are in the new package resolver, we wanna special handle it and return early. + if cli.resolve_deps { + new_package_resolver().await?; + return Ok(()); + } + if let Some(ref value) = cli.resolve_move_dependencies { check_sui_version(MINIMUM_BUILD_SUI_VERSION)?; // Resolver function that `sui move build` expects to call. - resolve_move_dependencies(&value).await?; + eprintln!("Resolving move dependencies for {}", value); + resolve_move_dependencies(value).await?; } else if let Some(command) = cli.command { let output = command.execute().await?; if cli.json { @@ -35,13 +49,10 @@ async fn main() -> Result<()> { println!("{}", output); } } else { - let cli = Cli::parse_from(&["mvr", "--help"]); - match cli.command { - Some(x) => { - let c = x.execute().await?; - println!("{:?}", c.to_string()); - } - None => {} + let cli = Cli::parse_from(["mvr", "--help"]); + if let Some(x) = cli.command { + let c = x.execute().await?; + println!("{:?}", c.to_string()); } } diff --git a/crates/mvr-cli/src/utils/api_data.rs b/crates/mvr-cli/src/types/api_data.rs similarity index 94% rename from crates/mvr-cli/src/utils/api_data.rs rename to crates/mvr-cli/src/types/api_data.rs index fdadfae8..d6c5e94c 100644 --- a/crates/mvr-cli/src/utils/api_data.rs +++ b/crates/mvr-cli/src/types/api_data.rs @@ -25,7 +25,7 @@ pub async fn query_package(name: &str, network: &Network) -> Result<(String, Pac let response = reqwest::get(format!( "{}/v1/names/{}", get_api_url(network)?, - versioned_name.to_string() + versioned_name )) .await .map_err(|e| CliError::Querying(e.to_string()))?; @@ -46,13 +46,9 @@ pub async fn query_package(name: &str, network: &Network) -> Result<(String, Pac } pub async fn resolve_name(name: &VersionedName, network: &Network) -> Result { - let response = reqwest::get(format!( - "{}/v1/resolution/{}", - get_api_url(network)?, - name.to_string() - )) - .await - .map_err(|e| CliError::Querying(e.to_string()))?; + let response = reqwest::get(format!("{}/v1/resolution/{}", get_api_url(network)?, name)) + .await + .map_err(|e| CliError::Querying(e.to_string()))?; if response.status() == reqwest::StatusCode::NOT_FOUND { bail!(CliError::NameNotExists( diff --git a/crates/mvr-cli/src/types/mod.rs b/crates/mvr-cli/src/types/mod.rs index d7ea9f25..601414fd 100644 --- a/crates/mvr-cli/src/types/mod.rs +++ b/crates/mvr-cli/src/types/mod.rs @@ -1,4 +1,6 @@ +pub mod api_data; pub mod api_types; +pub mod resolver_alt; use std::fmt; use std::str::FromStr; @@ -37,7 +39,7 @@ pub(crate) struct SuiConfig { envs: Vec, } -#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] pub enum Network { Mainnet, Testnet, diff --git a/crates/mvr-cli/src/types/resolver_alt.rs b/crates/mvr-cli/src/types/resolver_alt.rs new file mode 100644 index 00000000..47c3acc8 --- /dev/null +++ b/crates/mvr-cli/src/types/resolver_alt.rs @@ -0,0 +1,137 @@ +use std::{collections::BTreeMap, env, io::stdin, str::FromStr}; + +use anyhow::Result; +use jsonrpc::types::{BatchRequest, JsonRpcResult, RemoteError, RequestID, Response, TwoPointZero}; +use mvr_types::name::VersionedName; +use serde::Deserialize; +use yansi::Paint; + +use crate::types::{api_data::query_multiple_dependencies, MoveRegistryDependencies, Network}; + +#[derive(Deserialize, Debug)] +struct ResolveRequest { + #[serde(default)] + // We expect a "chain-id" populated here, or we'll resolve on all known chain ids (mainnet / testnet) + env: Option, + + // we expect the "data" to be a plain string, being a MVR Name. + data: String, +} + +/// [Experimental] +/// The package-alt resolver for packages. +/// Note: This does not provide validation for "IDs". A `validate` command needs to be implemented +/// for validation of expected IDs to occur. +pub async fn new_package_resolver() -> Result<()> { + let input = parse_input(); + let mut names = BTreeMap::new(); + let mut per_env = BTreeMap::new(); + + for (_, request) in &input { + let name = VersionedName::from_str(&request.data)?; + let normalized_network = get_normalized_network(&request.env.clone().unwrap_or_default())?; + + eprintln!( + "{}: {:?} {} {}", + "[mvr] RESOLVING".blue(), + request.data.blue().bold(), + "ON".blue(), + normalized_network.blue().bold(), + ); + + names + .entry(normalized_network) + .or_insert_with(Vec::new) + .push(name); + } + + for (network, names) in &names { + let response = query_multiple_dependencies( + MoveRegistryDependencies { + packages: names.iter().map(|n| n.to_string()).collect(), + }, + &network, + ) + .await?; + + per_env.insert(network, response); + } + + let responses: Vec> = input + .into_iter() + .map(|(id, request)| { + // TODO: properly propagate errors -- we can leave as is for now while we're testing pkg-alt. + let normalized_network = get_normalized_network(&request.env.unwrap_or_default()).expect("We should have a normalized network error by this point."); + let map = per_env.get(&normalized_network).expect("No response found for env"); + + let Some(response) = map.get(&request.data) else { + return format_result(id, JsonRpcResult::Err { + error: RemoteError { code: 404, message: format!("No name entries found for {}", request.data), data: None } + }); + }; + + let Some(git_info) = &response.git_info else { + return format_result(id, JsonRpcResult::Err { + error: RemoteError { code: 404, message: format!("Package with name {} does not have git info for env {}", request.data, normalized_network), data: None } + }); + }; + + format_result(id, JsonRpcResult::Ok { + result: serde_json::json!({ "git": git_info.repository_url, "rev": git_info.tag, "subdir": git_info.path }) + }) + }) + .collect(); + + let json = serde_json::to_string(&responses).unwrap_or_default(); + + println!("{json}"); + Ok(()) +} + +/// Read a [Request] from [stdin] +fn parse_input() -> BTreeMap { + let mut line = String::new(); + stdin().read_line(&mut line).expect("stdin can be read"); + + let batch: BatchRequest = serde_json::from_str(&line) + .expect("External resolver must be passed a JSON RPC batch request"); + + batch + .into_iter() + .map(|req| { + assert!(req.method == "resolve"); + (req.id, req.params) + }) + .collect() +} + +/// Returns the "normalized" network: +/// 1. If the chain-id of the env is known, then we return that. +/// 2. If the chain-id is not known, we try to get the `flag`-based setup. +/// 3. We error with the "original" error. +fn get_normalized_network(env: &str) -> Result { + let normalized_network = Network::try_from_chain_identifier(&env); + + if let Ok(normalized_network) = normalized_network { + return Ok(normalized_network); + } + + let fallback_network = env::var("MVR_FALLBACK_NETWORK") + .ok() + .map(|s| Network::from_str(&s)) + .transpose(); + + if let Ok(Some(fallback_network)) = fallback_network { + return Ok(fallback_network); + } + + Ok(normalized_network?) +} + +fn format_result(id: u64, result: JsonRpcResult) -> Response { + Response { + jsonrpc: TwoPointZero, + id, + result, + } +} diff --git a/crates/mvr-cli/src/utils/manifest.rs b/crates/mvr-cli/src/utils/manifest.rs index 59f8473e..25270dc0 100644 --- a/crates/mvr-cli/src/utils/manifest.rs +++ b/crates/mvr-cli/src/utils/manifest.rs @@ -50,7 +50,7 @@ impl MoveToml { ); new_dep_table.insert(RESOLVER_PREFIX_KEY, Value::InlineTable(r_table)); - dependencies.insert(&name, Item::Value(Value::InlineTable(new_dep_table))); + dependencies.insert(name, Item::Value(Value::InlineTable(new_dep_table))); Ok(()) } @@ -61,8 +61,7 @@ impl MoveToml { .get(RESOLVER_PREFIX_KEY) .and_then(|v| v.get(MVR_RESOLVER_KEY)) .and_then(|v| v.get(NETWORK_KEY)) - .map(|v| v.as_str()) - .flatten() + .and_then(|v| v.as_str()) .map(|s| s.to_string()) } diff --git a/crates/mvr-cli/src/utils/mod.rs b/crates/mvr-cli/src/utils/mod.rs index fe926d7d..005237d1 100644 --- a/crates/mvr-cli/src/utils/mod.rs +++ b/crates/mvr-cli/src/utils/mod.rs @@ -1,4 +1,3 @@ -pub mod api_data; pub mod git; pub mod manifest; pub mod sui_binary; diff --git a/crates/mvr-cli/src/utils/sui_binary.rs b/crates/mvr-cli/src/utils/sui_binary.rs index b94a5a0c..8d466d5a 100644 --- a/crates/mvr-cli/src/utils/sui_binary.rs +++ b/crates/mvr-cli/src/utils/sui_binary.rs @@ -101,7 +101,7 @@ pub fn get_active_network() -> Result { let cli_network = Network::try_from_chain_identifier(&chain_id); let Ok(cli_network) = cli_network else { - if !fallback_network.is_ok() { + if fallback_network.is_err() { bail!(cli_network.unwrap_err()); }