runtime_context.rs (5109B)
1 //! Sealed bootstrap binding for one canonical Myc service instance. 2 3 use core::fmt; 4 use std::{error::Error, path::Path}; 5 6 use radroots_runtime_paths::{ 7 RadrootsPathProfile, RadrootsPathResolver, RadrootsServiceInstanceArtifacts, RuntimeContext, 8 RuntimeContextBootstrap, RuntimeContextSource, ServiceId, default_service_instance_artifacts, 9 }; 10 11 use crate::{MycBootstrapProfileV1, MycCliInvocationV1}; 12 13 const MYC_SERVICE_ID: &str = "myc"; 14 15 /// Stable source-free classification for Myc runtime-context failures. 16 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 17 pub enum MycRuntimeContextErrorKind { 18 InvalidServiceIdentity, 19 InvalidBootstrapBinding, 20 PathSelection, 21 } 22 23 impl MycRuntimeContextErrorKind { 24 const fn message(self) -> &'static str { 25 match self { 26 Self::InvalidServiceIdentity => "Myc service identity is invalid", 27 Self::InvalidBootstrapBinding => "Myc bootstrap selectors are inconsistent", 28 Self::PathSelection => "Myc runtime path selection failed", 29 } 30 } 31 } 32 33 /// One redacted Myc runtime-context failure. 34 #[derive(Clone, Copy, PartialEq, Eq)] 35 pub struct MycRuntimeContextError { 36 kind: MycRuntimeContextErrorKind, 37 } 38 39 impl MycRuntimeContextError { 40 const fn new(kind: MycRuntimeContextErrorKind) -> Self { 41 Self { kind } 42 } 43 44 #[must_use] 45 pub const fn kind(self) -> MycRuntimeContextErrorKind { 46 self.kind 47 } 48 } 49 50 impl fmt::Debug for MycRuntimeContextError { 51 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 52 formatter 53 .debug_struct("MycRuntimeContextError") 54 .field("kind", &self.kind) 55 .finish() 56 } 57 } 58 59 impl fmt::Display for MycRuntimeContextError { 60 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 61 formatter.write_str(self.kind.message()) 62 } 63 } 64 65 impl Error for MycRuntimeContextError {} 66 67 /// Immutable canonical paths and bootstrap selection for one Myc instance. 68 /// 69 /// Construction is sealed to the validated CLI invocation and the shared 70 /// runtime-path resolver. Callers cannot forge an alternate service identity, 71 /// path set, artifact name, or selected configuration path: 72 /// 73 /// ```compile_fail 74 /// use myc::MycRuntimeContext; 75 /// 76 /// let _ = MycRuntimeContext { 77 /// context: todo!(), 78 /// artifacts: todo!(), 79 /// selected_config_path: todo!(), 80 /// profile: todo!(), 81 /// }; 82 /// ``` 83 #[derive(Clone, PartialEq, Eq)] 84 pub struct MycRuntimeContext { 85 context: RuntimeContext, 86 artifacts: RadrootsServiceInstanceArtifacts, 87 selected_config_path: std::path::PathBuf, 88 profile: MycBootstrapProfileV1, 89 } 90 91 impl MycRuntimeContext { 92 #[must_use] 93 pub fn context(&self) -> &RuntimeContext { 94 &self.context 95 } 96 97 #[must_use] 98 pub fn artifacts(&self) -> &RadrootsServiceInstanceArtifacts { 99 &self.artifacts 100 } 101 102 #[must_use] 103 pub fn selected_config_path(&self) -> &Path { 104 &self.selected_config_path 105 } 106 107 #[must_use] 108 pub const fn profile(&self) -> MycBootstrapProfileV1 { 109 self.profile 110 } 111 } 112 113 impl fmt::Debug for MycRuntimeContext { 114 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 115 formatter 116 .debug_struct("MycRuntimeContext") 117 .field("profile", &self.profile) 118 .field("service", &MYC_SERVICE_ID) 119 .field("instance", &"[redacted]") 120 .field("paths", &"[redacted]") 121 .finish() 122 } 123 } 124 125 /// Resolves one validated CLI selection into the sole Myc path authority. 126 pub fn resolve_myc_runtime_context( 127 resolver: &RadrootsPathResolver, 128 invocation: &MycCliInvocationV1, 129 ) -> Result<MycRuntimeContext, MycRuntimeContextError> { 130 let profile = invocation.profile(); 131 let path_profile = match profile { 132 MycBootstrapProfileV1::ServiceHost => RadrootsPathProfile::ServiceHost, 133 MycBootstrapProfileV1::Interactive => RadrootsPathProfile::InteractiveUser, 134 MycBootstrapProfileV1::RepoLocal => RadrootsPathProfile::RepoLocal, 135 }; 136 let bootstrap = RuntimeContextBootstrap::new( 137 path_profile, 138 invocation.repo_local_root().map(Path::to_path_buf), 139 RuntimeContextSource::BootstrapCli, 140 RuntimeContextSource::BootstrapCli, 141 ) 142 .map_err(|_| { 143 MycRuntimeContextError::new(MycRuntimeContextErrorKind::InvalidBootstrapBinding) 144 })?; 145 let service = ServiceId::new(MYC_SERVICE_ID).map_err(|_| { 146 MycRuntimeContextError::new(MycRuntimeContextErrorKind::InvalidServiceIdentity) 147 })?; 148 let context = 149 RuntimeContext::resolve(resolver, bootstrap, service, invocation.instance().clone()) 150 .map_err(|_| MycRuntimeContextError::new(MycRuntimeContextErrorKind::PathSelection))?; 151 let artifacts = default_service_instance_artifacts(context.paths()); 152 let selected_config_path = invocation 153 .config_path() 154 .map(Path::to_path_buf) 155 .unwrap_or_else(|| artifacts.config().to_path_buf()); 156 157 Ok(MycRuntimeContext { 158 context, 159 artifacts, 160 selected_config_path, 161 profile, 162 }) 163 }