cloud-config/src/backend.rs
136 lines
use codex_backend_client::Client as BackendClient;use codex_backend_client::ConfigBundleResponse;use codex_backend_client::DeliveredTomlFragment;use codex_config::CloudConfigBundle;use codex_config::CloudConfigFragment;use codex_config::CloudConfigTomlBundle;use codex_config::CloudRequirementsFragment;use codex_config::CloudRequirementsTomlBundle;use codex_login::CodexAuth;use std::future::Future;#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub(crate) enum RetryableFailureKind { BackendClientInit, Request { status_code: Option<u16> },}impl RetryableFailureKind { pub(crate) fn status_code(self) -> Option<u16> { match self { Self::BackendClientInit => None, Self::Request { status_code } => status_code, } }}#[derive(Clone, Debug, Eq, PartialEq)]pub(crate) enum BundleRequestError { Retryable(RetryableFailureKind), Unauthorized { status_code: Option<u16>, message: String, },}/// Retrieves one cloud config bundle from the backend.////// Implementations should return the backend-selected bundle exactly as delivered and leave/// validation, caching, and config/requirements parsing decisions to the service layer.pub(crate) trait BundleClient: Send + Sync { fn get_bundle( &self, auth: &CodexAuth, ) -> impl Future<Output = Result<CloudConfigBundle, BundleRequestError>> + Send;}pub(crate) struct BackendBundleClient { base_url: String,}impl BackendBundleClient { pub(crate) fn new(base_url: String) -> Self { Self { base_url } }}impl BundleClient for BackendBundleClient { async fn get_bundle(&self, auth: &CodexAuth) -> Result<CloudConfigBundle, BundleRequestError> { let client = BackendClient::from_auth(self.base_url.clone(), auth) .inspect_err(|err| { tracing::warn!( error = %err, "Failed to construct backend client for cloud config bundle" ); }) .map_err(|_| BundleRequestError::Retryable(RetryableFailureKind::BackendClientInit))?; let response = client .get_config_bundle() .await .inspect_err(|err| { tracing::warn!(error = %err, "Failed to fetch cloud config bundle"); }) .map_err(|err| { let status_code = err.status().map(|status| status.as_u16()); if err.is_unauthorized() { BundleRequestError::Unauthorized { status_code, message: err.to_string(), } } else { BundleRequestError::Retryable(RetryableFailureKind::Request { status_code }) } })?; Ok(bundle_from_response(response)) }}pub(crate) fn bundle_from_response(response: ConfigBundleResponse) -> CloudConfigBundle { let config_toml = response .config_toml .flatten() .map(|config_toml| *config_toml) .and_then(|config_toml| config_toml.enterprise_managed.flatten()) .unwrap_or_default() .into_iter() .map(config_fragment_from_delivered) .collect(); let requirements_toml = response .requirements_toml .flatten() .map(|requirements_toml| *requirements_toml) .and_then(|requirements_toml| requirements_toml.enterprise_managed.flatten()) .unwrap_or_default() .into_iter() .map(requirements_fragment_from_delivered) .collect(); CloudConfigBundle { config_toml: CloudConfigTomlBundle { enterprise_managed: config_toml, }, requirements_toml: CloudRequirementsTomlBundle { enterprise_managed: requirements_toml, }, }}fn config_fragment_from_delivered(fragment: DeliveredTomlFragment) -> CloudConfigFragment { CloudConfigFragment { id: fragment.id, name: fragment.name, contents: fragment.contents, }}fn requirements_fragment_from_delivered( fragment: DeliveredTomlFragment,) -> CloudRequirementsFragment { CloudRequirementsFragment { id: fragment.id, name: fragment.name, contents: fragment.contents, }}