diff --git a/CHANGELOG.md b/CHANGELOG.md index e15cb1432d..ad4184eb8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -102,6 +102,7 @@ The minor version will be incremented upon a breaking change and the patch versi - cli: Avoid extra IDL generation during `verify` ([#3398](https://github.com/coral-xyz/anchor/pull/3398)). - lang: Require `zero` accounts to be unique ([#3409](https://github.com/coral-xyz/anchor/pull/3409)). - lang: Deduplicate `zero` accounts against `init` accounts ([#3422](https://github.com/coral-xyz/anchor/pull/3422)). +- cli: Fix custom `provider.cluster` ([#3428](https://github.com/coral-xyz/anchor/pull/3428)). ### Breaking diff --git a/cli/src/config.rs b/cli/src/config.rs index c8a579f3a2..f38100aba5 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -7,7 +7,8 @@ use dirs::home_dir; use heck::ToSnakeCase; use reqwest::Url; use serde::de::{self, MapAccess, Visitor}; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::ser::SerializeMap; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use solana_cli_config::{Config as SolanaConfig, CONFIG_FILE}; use solana_sdk::clock::Slot; use solana_sdk::pubkey::Pubkey; @@ -590,11 +591,29 @@ struct _Config { #[derive(Debug, Serialize, Deserialize)] struct Provider { - #[serde(deserialize_with = "des_cluster")] + #[serde(serialize_with = "ser_cluster", deserialize_with = "des_cluster")] cluster: Cluster, wallet: String, } +fn ser_cluster(cluster: &Cluster, s: S) -> Result { + match cluster { + Cluster::Custom(http, ws) => { + match (Url::parse(http), Url::parse(ws)) { + // If `ws` was derived from `http`, serialize `http` as string + (Ok(h), Ok(w)) if h.domain() == w.domain() => s.serialize_str(http), + _ => { + let mut map = s.serialize_map(Some(2))?; + map.serialize_entry("http", http)?; + map.serialize_entry("ws", ws)?; + map.end() + } + } + } + _ => s.serialize_str(&cluster.to_string()), + } +} + fn des_cluster<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -1465,15 +1484,34 @@ mod tests { wallet = \"id.json\" "; - const CUSTOM_CONFIG: &str = " + #[test] + fn parse_custom_cluster_str() { + let config = Config::from_str( + " [provider] - cluster = { http = \"http://my-url.com\", ws = \"ws://my-url.com\" } + cluster = \"http://my-url.com\" wallet = \"id.json\" - "; + ", + ) + .unwrap(); + assert!(!config.features.skip_lint); + + // Make sure the layout of `provider.cluster` stays the same after serialization + assert!(config + .to_string() + .contains(r#"cluster = "http://my-url.com""#)); + } #[test] - fn parse_custom_cluster() { - let config = Config::from_str(CUSTOM_CONFIG).unwrap(); + fn parse_custom_cluster_map() { + let config = Config::from_str( + " + [provider] + cluster = { http = \"http://my-url.com\", ws = \"ws://my-url.com\" } + wallet = \"id.json\" + ", + ) + .unwrap(); assert!(!config.features.skip_lint); }