From 807663e298215dc0f743002399cda6ea3f0534b8 Mon Sep 17 00:00:00 2001 From: "Sanket M." Date: Fri, 7 Aug 2026 18:08:01 +0530 Subject: [PATCH] feat(environments): read DevX Core URL from config (DEVX-793) --- rust/src/environments/config.rs | 18 ++++++ rust/src/environments/devx_core.rs | 83 ++++++++++++++++---------- rust/src/environments/mod.rs | 94 +++++++++++++++++++++++++++++- 3 files changed, 163 insertions(+), 32 deletions(-) diff --git a/rust/src/environments/config.rs b/rust/src/environments/config.rs index 29c6323..951d6d2 100644 --- a/rust/src/environments/config.rs +++ b/rust/src/environments/config.rs @@ -58,6 +58,17 @@ 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`; built-in environments + /// receive their defaults from `BaseEnvConfig`. Shell overrides are + /// applied separately by `devx_core_url` so their legacy precedence is + /// preserved. + #[env_config( + from_toml = parse_url_from_toml, + default_fn = default_devx_core_url + )] + pub devx_core_url: String, } /// `name`'s `default_fn`: the field itself is never set by any real TOML/env @@ -99,6 +110,13 @@ fn default_account_url(sources: &SourceChain<'_>) -> String { derive_account_url(sources.env_name().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 report a missing URL through `devx_core_url`. + String::new() +} + fn derive_account_url(env_name: &str) -> String { if env_name == "prod" { return "https://account.godaddy.com".to_owned(); diff --git a/rust/src/environments/devx_core.rs b/rust/src/environments/devx_core.rs index 6991f1f..6b7aefe 100644 --- a/rust/src/environments/devx_core.rs +++ b/rust/src/environments/devx_core.rs @@ -1,35 +1,31 @@ //! DevX Core API gateway base-URL resolution per environment. use super::config::clean_url; -use super::env_prefix; - -/// 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. -const BUILTIN_DEVX_CORE_URLS: &[(&str, &str)] = &[ - ("ote", "https://api.developer.commerce.ote-godaddy.com"), - ("prod", "https://api.developer.commerce.godaddy.com"), -]; +use super::{env_prefix, resolve}; /// 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` 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 value. `prod` +/// and `ote` receive their defaults from the compiled-in environment config. 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)) } #[cfg(test)] @@ -39,21 +35,42 @@ mod tests { #[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("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", |key| { - (key == "DEVX_CORE_URL").then(|| " http://localhost:4000/ ".to_owned()) - }) + 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") ); @@ -62,11 +79,15 @@ mod tests { #[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") ); @@ -74,6 +95,6 @@ mod tests { #[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); } } diff --git a/rust/src/environments/mod.rs b/rust/src/environments/mod.rs index 43e24bb..3f88f97 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] @@ -41,11 +42,12 @@ pub use devx_core::devx_core_url; 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. @@ -56,6 +58,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(), }, ), ( @@ -63,6 +66,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(), }, ), ] @@ -167,6 +171,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"); @@ -176,6 +181,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] @@ -202,6 +211,89 @@ 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_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_malformed_file_layer_auth_url_override_for_a_builtin() { let _g = ENV_LOCK