From f8290863cfb130659b666864a448bcf82809adcd Mon Sep 17 00:00:00 2001 From: "Sanket M." Date: Fri, 7 Aug 2026 18:08:01 +0530 Subject: [PATCH 1/2] feat(environments): read DevX Core URL from config (DEVX-793) --- rust/src/environments/mod.rs | 128 ++++++++++++++++++++++++++++------- 1 file changed, 103 insertions(+), 25 deletions(-) diff --git a/rust/src/environments/mod.rs b/rust/src/environments/mod.rs index 0e3e0313..f6736820 100644 --- a/rust/src/environments/mod.rs +++ b/rust/src/environments/mod.rs @@ -18,6 +18,7 @@ //! ```toml //! [dev] //! api_url = "https://api.dev-godaddy.com" +//! devx_core_url = "https://api.developer.commerce.dev-godaddy.com" //! min_stage = "experimental" //! //! [staging.feature_overrides] @@ -67,8 +68,7 @@ pub const DEFAULT_OAUTH_SCOPES: &[&str] = &[ pub const REDIRECT_URI: &str = "http://localhost:7443/callback"; pub const APP_ID: &str = "gddy"; -/// DevX Core API gateway base URL for each compiled-in builtin, consulted by -/// [`devx_core_url_with`] only after both env-var override tiers miss. +/// DevX Core API gateway base URL for each compiled-in builtin. const BUILTIN_DEVX_CORE_URLS: &[(&str, &str)] = &[ ("ote", "https://api.developer.commerce.ote-godaddy.com"), ("prod", "https://api.developer.commerce.godaddy.com"), @@ -127,6 +127,16 @@ pub struct GddyEnvConfig { default_fn = default_account_url )] pub account_url: String, + + /// Base URL for the DevX Core API gateway used by onboarding. Custom + /// environments set this in `environments.toml`; `prod` and `ote` retain + /// their compiled-in endpoints. Shell overrides are applied separately by + /// [`devx_core_url`] so their legacy names and precedence are preserved. + #[env_config( + from_toml = parse_url_from_toml, + default_fn = default_devx_core_url + )] + pub devx_core_url: String, } pub fn env_prefix(name: &str) -> String { @@ -172,6 +182,15 @@ fn default_account_url(sources: &SourceChain<'_>) -> String { derive_account_url(sources.env_name().unwrap_or_default()) } +fn default_devx_core_url(sources: &SourceChain<'_>) -> String { + let name = sources.env_name().unwrap_or_default(); + BUILTIN_DEVX_CORE_URLS + .iter() + .find(|(builtin_name, _)| *builtin_name == name) + .map(|(_, url)| (*url).to_owned()) + .unwrap_or_default() +} + fn derive_account_url(env_name: &str) -> String { if env_name == "prod" { return "https://account.godaddy.com".to_owned(); @@ -289,24 +308,28 @@ fn clean_url(raw: &str) -> Option { /// Base URL for the DevX Core API gateway for the given environment. /// -/// Custom environments must set `_DEVX_CORE_URL` (for example, -/// `DEV_DEVX_CORE_URL`) or the global `DEVX_CORE_URL`. `prod` and `ote` use -/// their compiled-in endpoints unless either variable overrides them. +/// The configured `devx_core_url` from `environments.toml` is the default for +/// custom environments. `_DEVX_CORE_URL` (for example, +/// `DEV_DEVX_CORE_URL`) and the global `DEVX_CORE_URL` shell variable retain +/// precedence over that file value. `prod` and `ote` use compiled-in defaults +/// unless the file or either shell override supplies another URL. pub fn devx_core_url(name: &str) -> Option { - devx_core_url_with(name, |key| std::env::var(key).ok()) + let configured = resolve(name) + .ok() + .and_then(|config| clean_url(&config.devx_core_url)); + devx_core_url_with(name, configured.as_deref(), |key| std::env::var(key).ok()) } -fn devx_core_url_with(name: &str, var: impl Fn(&str) -> Option) -> Option { +fn devx_core_url_with( + name: &str, + configured: Option<&str>, + var: impl Fn(&str) -> Option, +) -> Option { let prefix = env_prefix(name); var(&format!("{prefix}_DEVX_CORE_URL")) .and_then(|value| clean_url(&value)) .or_else(|| var("DEVX_CORE_URL").and_then(|value| clean_url(&value))) - .or_else(|| { - BUILTIN_DEVX_CORE_URLS - .iter() - .find(|(n, _)| *n == name) - .map(|(_, url)| (*url).to_owned()) - }) + .or_else(|| configured.and_then(clean_url)) } /// Validates a candidate URL string. `EnvConfig` `from_env` shared by every @@ -452,6 +475,7 @@ mod tests { [dev] api_url = "https://api.dev-godaddy.com" client_id = "dev-client" +devx_core_url = "https://api.developer.commerce.dev-godaddy.com" "#, ) .expect("write file"); @@ -461,6 +485,10 @@ client_id = "dev-client" assert_eq!(resolved.domains_api_url, "https://api.dev-godaddy.com"); assert_eq!(resolved.account_url, "https://account.dev-godaddy.com"); + assert_eq!( + resolved.devx_core_url, + "https://api.developer.commerce.dev-godaddy.com" + ); } #[test] @@ -487,6 +515,31 @@ client_id = "dev-client" assert!(err.to_string().contains("api_url")); } + #[test] + fn register_rejects_a_malformed_file_layer_devx_core_url() { + let _g = ENV_LOCK + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let dir = tempfile::tempdir().expect("tempdir"); + let file = dir.path().join("environments.toml"); + std::fs::write( + &file, + r#" +[dev] +api_url = "https://api.dev-godaddy.com" +client_id = "dev-client" +devx_core_url = "not-a-url" +"#, + ) + .expect("write file"); + + let envs = register(Environments::new("prod").with_config_file_path_override(file)); + let err = envs + .resolve::("dev") + .expect_err("a malformed devx_core_url must be a hard error"); + assert!(err.to_string().contains("devx_core_url")); + } + #[test] fn register_rejects_a_malformed_file_layer_auth_url_override_for_a_builtin() { let _g = ENV_LOCK @@ -919,21 +972,42 @@ auth_url = "not-a-url" #[test] fn devx_core_url_uses_prod_and_ote_builtins() { assert_eq!( - devx_core_url_with("prod", |_| None).as_deref(), + devx_core_url_with( + "prod", + Some("https://api.developer.commerce.godaddy.com"), + |_| None, + ) + .as_deref(), Some("https://api.developer.commerce.godaddy.com") ); assert_eq!( - devx_core_url_with("ote", |_| None).as_deref(), + devx_core_url_with( + "ote", + Some("https://api.developer.commerce.ote-godaddy.com"), + |_| None, + ) + .as_deref(), Some("https://api.developer.commerce.ote-godaddy.com") ); } #[test] - fn devx_core_url_global_override_wins() { + fn devx_core_url_uses_the_environments_toml_value_for_a_custom_env() { assert_eq!( - devx_core_url_with("prod", |key| { - (key == "DEVX_CORE_URL").then(|| " http://localhost:4000/ ".to_owned()) - }) + devx_core_url_with("dev", Some(" https://dev-core.example.test/ "), |_| None,) + .as_deref(), + Some("https://dev-core.example.test") + ); + } + + #[test] + fn devx_core_url_global_override_wins_over_the_environments_toml_value() { + assert_eq!( + devx_core_url_with( + "prod", + Some("https://configured-core.example.test"), + |key| { (key == "DEVX_CORE_URL").then(|| " http://localhost:4000/ ".to_owned()) }, + ) .as_deref(), Some("http://localhost:4000") ); @@ -942,11 +1016,15 @@ auth_url = "not-a-url" #[test] fn devx_core_url_per_environment_override_wins_over_global() { assert_eq!( - devx_core_url_with("dev", |key| match key { - "DEV_DEVX_CORE_URL" => Some("https://dev-core.example.test/".to_owned()), - "DEVX_CORE_URL" => Some("https://shared-core.example.test".to_owned()), - _ => None, - }) + devx_core_url_with( + "dev", + Some("https://configured-core.example.test"), + |key| match key { + "DEV_DEVX_CORE_URL" => Some("https://dev-core.example.test/".to_owned()), + "DEVX_CORE_URL" => Some("https://shared-core.example.test".to_owned()), + _ => None, + }, + ) .as_deref(), Some("https://dev-core.example.test") ); @@ -954,6 +1032,6 @@ auth_url = "not-a-url" #[test] fn devx_core_url_custom_env_requires_override() { - assert_eq!(devx_core_url_with("dev", |_| None), None); + assert_eq!(devx_core_url_with("dev", None, |_| None), None); } } From 296a0861139f00328c456d3b9970bb22cd13bc8f Mon Sep 17 00:00:00 2001 From: "Sanket M." Date: Mon, 10 Aug 2026 23:27:16 +0530 Subject: [PATCH 2/2] refactor(environments): unify builtin DevX Core URLs --- rust/src/environments/mod.rs | 88 +++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 17 deletions(-) diff --git a/rust/src/environments/mod.rs b/rust/src/environments/mod.rs index f6736820..78004413 100644 --- a/rust/src/environments/mod.rs +++ b/rust/src/environments/mod.rs @@ -32,11 +32,12 @@ use cli_engine::{ConfigSource, EnvConfig, SourceChain}; pub const DEFAULT_ENV: &str = "prod"; -/// The two fields a compiled-in environment actually sets. +/// The public, non-secret values a compiled-in environment sets. #[derive(Debug, Clone, EnvConfig)] struct BaseEnvConfig { api_url: String, client_id: String, + devx_core_url: String, } /// The compiled-in `ote`/`prod` environments. @@ -47,6 +48,7 @@ static BUILTIN_ENVS: LazyLock> = LazyLock::ne BaseEnvConfig { api_url: "https://api.ote-godaddy.com".to_owned(), client_id: "91660d79-c909-426c-b5c8-e0f575e8fcd2".to_owned(), + devx_core_url: "https://api.developer.commerce.ote-godaddy.com".to_owned(), }, ), ( @@ -54,6 +56,7 @@ static BUILTIN_ENVS: LazyLock> = LazyLock::ne BaseEnvConfig { api_url: "https://api.godaddy.com".to_owned(), client_id: "bc87f347-af82-4892-833f-818f54a0e79e".to_owned(), + devx_core_url: "https://api.developer.commerce.godaddy.com".to_owned(), }, ), ] @@ -68,12 +71,6 @@ pub const DEFAULT_OAUTH_SCOPES: &[&str] = &[ pub const REDIRECT_URI: &str = "http://localhost:7443/callback"; pub const APP_ID: &str = "gddy"; -/// DevX Core API gateway base URL for each compiled-in builtin. -const BUILTIN_DEVX_CORE_URLS: &[(&str, &str)] = &[ - ("ote", "https://api.developer.commerce.ote-godaddy.com"), - ("prod", "https://api.developer.commerce.godaddy.com"), -]; - /// A fully-resolved environment config #[derive(Debug, Clone, Default, EnvConfig)] pub struct GddyEnvConfig { @@ -129,9 +126,10 @@ pub struct GddyEnvConfig { pub account_url: String, /// Base URL for the DevX Core API gateway used by onboarding. Custom - /// environments set this in `environments.toml`; `prod` and `ote` retain - /// their compiled-in endpoints. Shell overrides are applied separately by - /// [`devx_core_url`] so their legacy names and precedence are preserved. + /// environments set this in `environments.toml`; `prod` and `ote` receive + /// their defaults through [`BUILTIN_ENVS`]. Shell overrides are applied + /// separately by [`devx_core_url`] so their legacy names and precedence + /// are preserved. #[env_config( from_toml = parse_url_from_toml, default_fn = default_devx_core_url @@ -182,13 +180,11 @@ fn default_account_url(sources: &SourceChain<'_>) -> String { derive_account_url(sources.env_name().unwrap_or_default()) } -fn default_devx_core_url(sources: &SourceChain<'_>) -> String { - let name = sources.env_name().unwrap_or_default(); - BUILTIN_DEVX_CORE_URLS - .iter() - .find(|(builtin_name, _)| *builtin_name == name) - .map(|(_, url)| (*url).to_owned()) - .unwrap_or_default() +fn default_devx_core_url(_sources: &SourceChain<'_>) -> String { + // A custom environment must configure this value explicitly. The empty + // default keeps the field optional for unrelated CLI commands; callers + // that require DevX Core use `devx_core_url()` and report it as missing. + String::new() } fn derive_account_url(env_name: &str) -> String { @@ -491,6 +487,64 @@ devx_core_url = "https://api.developer.commerce.dev-godaddy.com" ); } + #[test] + fn register_resolves_builtin_devx_core_urls() { + let _g = ENV_LOCK + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let dir = tempfile::tempdir().expect("tempdir"); + let missing_file = dir.path().join("environments.toml"); + let envs = register(Environments::new("prod").with_config_file_path_override(missing_file)); + + assert_eq!( + envs.resolve::("ote") + .expect("ote resolves") + .devx_core_url, + "https://api.developer.commerce.ote-godaddy.com" + ); + assert_eq!( + envs.resolve::("prod") + .expect("prod resolves") + .devx_core_url, + "https://api.developer.commerce.godaddy.com" + ); + } + + #[test] + fn register_file_layer_overrides_builtin_devx_core_url() { + let _g = ENV_LOCK + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let dir = tempfile::tempdir().expect("tempdir"); + let file = dir.path().join("environments.toml"); + std::fs::write( + &file, + r#" +[ote] +devx_core_url = "https://core.override.example.test" + +[prod] +devx_core_url = "https://core.prod-override.example.test" +"#, + ) + .expect("write file"); + + let envs = register(Environments::new("prod").with_config_file_path_override(file)); + + assert_eq!( + envs.resolve::("ote") + .expect("ote resolves") + .devx_core_url, + "https://core.override.example.test" + ); + assert_eq!( + envs.resolve::("prod") + .expect("prod resolves") + .devx_core_url, + "https://core.prod-override.example.test" + ); + } + #[test] fn register_rejects_a_file_only_environments_malformed_api_url() { let _g = ENV_LOCK