diff --git a/rust/src/environments/mod.rs b/rust/src/environments/mod.rs index 0e3e031..f673682 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); } }