Codex Handbook
config/src/cloud_config_bundle.rs 214 lines
//! Cloud config bundle domain model and shared in-memory loader.//!//! The backend bundle groups cloud-delivered config and requirements fragments//! by source bucket. `CloudConfigBundleLayers` converts those raw buckets into//! layer entries while preserving each bucket's insertion semantics.use crate::CloudConfigFragment;use crate::ConfigLayerEntry;use crate::RequirementSource;use crate::RequirementsLayerEntry;use crate::cloud_config_layers::CloudConfigLayerError;use crate::cloud_config_layers::cloud_config_layers_from_fragments_strict;use crate::cloud_config_layers_from_fragments;use codex_utils_absolute_path::AbsolutePathBuf;use futures::future::BoxFuture;use futures::future::FutureExt;use futures::future::Shared;use serde::Deserialize;use serde::Serialize;use std::fmt;use std::future::Future;use thiserror::Error;#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]pub struct CloudConfigBundle {    pub config_toml: CloudConfigTomlBundle,    pub requirements_toml: CloudRequirementsTomlBundle,}impl CloudConfigBundle {    pub fn is_empty(&self) -> bool {        let CloudConfigBundle {            config_toml,            requirements_toml,        } = self;        let CloudConfigTomlBundle {            enterprise_managed: config_enterprise_managed,        } = config_toml;        let CloudRequirementsTomlBundle {            enterprise_managed: requirements_enterprise_managed,        } = requirements_toml;        config_enterprise_managed.is_empty() && requirements_enterprise_managed.is_empty()    }}#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]pub struct CloudConfigTomlBundle {    pub enterprise_managed: Vec<CloudConfigFragment>,}#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]pub struct CloudRequirementsTomlBundle {    pub enterprise_managed: Vec<CloudRequirementsFragment>,}#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]pub struct CloudRequirementsFragment {    pub id: String,    pub name: String,    pub contents: String,}/// Cloud config bundle converted into semantic layer buckets.////// This is not a final config stack. Callers still decide where each bucket is/// inserted relative to local/system/user layers.#[derive(Clone, Debug)]pub struct CloudConfigBundleLayers {    /// Enterprise-managed config layers in `ConfigLayerStack` order.    pub enterprise_managed_config: Vec<ConfigLayerEntry>,    /// Enterprise-managed requirements layers in requirements layer merge order.    pub enterprise_managed_requirements: Vec<RequirementsLayerEntry>,}impl CloudConfigBundleLayers {    pub fn from_bundle(        bundle: CloudConfigBundle,        base_dir: &AbsolutePathBuf,    ) -> Result<Self, CloudConfigLayerError> {        Self::from_bundle_impl(bundle, base_dir, /*strict_config*/ false)    }    pub fn from_bundle_strict_config(        bundle: CloudConfigBundle,        base_dir: &AbsolutePathBuf,    ) -> Result<Self, CloudConfigLayerError> {        Self::from_bundle_impl(bundle, base_dir, /*strict_config*/ true)    }    fn from_bundle_impl(        bundle: CloudConfigBundle,        base_dir: &AbsolutePathBuf,        strict_config: bool,    ) -> Result<Self, CloudConfigLayerError> {        // Keep this destructuring exhaustive so adding a new bundle bucket forces        // an explicit choice about how it becomes layer data.        let CloudConfigBundle {            config_toml:                CloudConfigTomlBundle {                    enterprise_managed: config_enterprise_managed,                },            requirements_toml:                CloudRequirementsTomlBundle {                    enterprise_managed: requirements_enterprise_managed,                },        } = bundle;        let enterprise_managed_config = if strict_config {            cloud_config_layers_from_fragments_strict(config_enterprise_managed, base_dir)?        } else {            cloud_config_layers_from_fragments(config_enterprise_managed, base_dir)?        };        let mut enterprise_managed_requirements = requirements_enterprise_managed            .into_iter()            .map(|fragment| {                RequirementsLayerEntry::from_toml(                    RequirementSource::EnterpriseManaged {                        id: fragment.id,                        name: fragment.name,                    },                    fragment.contents,                )                .with_base_dir(base_dir.clone())            })            .collect::<Vec<_>>();        // Bundle fragments arrive highest-priority first, while requirements        // layers are merged lowest-priority to highest-priority.        enterprise_managed_requirements.reverse();        Ok(Self {            enterprise_managed_config,            enterprise_managed_requirements,        })    }}#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum CloudConfigBundleLoadErrorCode {    Auth,    Timeout,    RequestFailed,    InvalidBundle,    Internal,}#[derive(Clone, Debug, Eq, Error, PartialEq)]#[error("{message}")]pub struct CloudConfigBundleLoadError {    code: CloudConfigBundleLoadErrorCode,    message: String,    status_code: Option<u16>,}impl CloudConfigBundleLoadError {    pub fn new(        code: CloudConfigBundleLoadErrorCode,        status_code: Option<u16>,        message: impl Into<String>,    ) -> Self {        Self {            code,            message: message.into(),            status_code,        }    }    pub fn code(&self) -> CloudConfigBundleLoadErrorCode {        self.code    }    pub fn status_code(&self) -> Option<u16> {        self.status_code    }}#[derive(Clone)]pub struct CloudConfigBundleLoader {    fut: Shared<BoxFuture<'static, Result<Option<CloudConfigBundle>, CloudConfigBundleLoadError>>>,}impl CloudConfigBundleLoader {    pub fn new<F>(fut: F) -> Self    where        F: Future<Output = Result<Option<CloudConfigBundle>, CloudConfigBundleLoadError>>            + Send            + 'static,    {        Self {            fut: fut.boxed().shared(),        }    }    pub async fn get(&self) -> Result<Option<CloudConfigBundle>, CloudConfigBundleLoadError> {        self.fut.clone().await    }}impl fmt::Debug for CloudConfigBundleLoader {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        f.debug_struct("CloudConfigBundleLoader").finish()    }}impl Default for CloudConfigBundleLoader {    fn default() -> Self {        Self::new(async { Ok(None) })    }}#[cfg(test)]#[path = "cloud_config_bundle_tests.rs"]mod tests;