resolve.rs (7673B)
1 use crate::error::RadrootsRuntimeDistributionError; 2 use crate::model::{ 3 ArtifactAdapter, RadrootsRuntimeDistributionContract, RuntimeDistributionEntry, TargetSpec, 4 }; 5 6 pub const RUNTIME_DISTRIBUTION_SCHEMA: &str = "radroots-runtime-distribution"; 7 8 #[derive(Debug, Clone, PartialEq, Eq)] 9 pub struct RuntimeArtifactRequest<'a> { 10 pub runtime_id: &'a str, 11 pub os: &'a str, 12 pub arch: &'a str, 13 pub version: &'a str, 14 pub channel: Option<&'a str>, 15 } 16 17 #[derive(Debug, Clone, PartialEq, Eq)] 18 pub struct ResolvedRuntimeArtifact { 19 pub runtime_id: String, 20 pub release_unit: String, 21 pub package_name: String, 22 pub binary_name: Option<String>, 23 pub artifact_adapter: String, 24 pub channel: String, 25 pub version: String, 26 pub target_id: String, 27 pub os: String, 28 pub arch: String, 29 pub archive_format: String, 30 pub archive_extension: String, 31 pub artifact_stem: String, 32 pub artifact_file_name: String, 33 } 34 35 #[derive(Debug, Clone)] 36 pub struct RadrootsRuntimeDistributionResolver { 37 contract: RadrootsRuntimeDistributionContract, 38 } 39 40 impl RadrootsRuntimeDistributionResolver { 41 pub fn parse_str(raw: &str) -> Result<Self, RadrootsRuntimeDistributionError> { 42 let contract = toml::from_str::<RadrootsRuntimeDistributionContract>(raw) 43 .map_err(|err| RadrootsRuntimeDistributionError::Parse(err.to_string()))?; 44 Self::new(contract) 45 } 46 47 pub fn new( 48 contract: RadrootsRuntimeDistributionContract, 49 ) -> Result<Self, RadrootsRuntimeDistributionError> { 50 if contract.schema != RUNTIME_DISTRIBUTION_SCHEMA { 51 return Err(RadrootsRuntimeDistributionError::UnexpectedSchema { 52 expected: RUNTIME_DISTRIBUTION_SCHEMA, 53 found: contract.schema.clone(), 54 }); 55 } 56 Ok(Self { contract }) 57 } 58 59 pub fn contract(&self) -> &RadrootsRuntimeDistributionContract { 60 &self.contract 61 } 62 63 pub fn resolve_artifact( 64 &self, 65 request: &RuntimeArtifactRequest<'_>, 66 ) -> Result<ResolvedRuntimeArtifact, RadrootsRuntimeDistributionError> { 67 let runtime = self 68 .contract 69 .runtime 70 .iter() 71 .find(|runtime| runtime.id == request.runtime_id) 72 .ok_or_else(|| { 73 RadrootsRuntimeDistributionError::UnknownRuntime(request.runtime_id.to_string()) 74 })?; 75 76 if !runtime.human_installable { 77 return Err(RadrootsRuntimeDistributionError::RuntimeNotInstallable( 78 runtime.id.clone(), 79 )); 80 } 81 82 let channel = request.channel.unwrap_or(runtime.default_channel.as_str()); 83 self.ensure_channel_is_active(channel)?; 84 85 let target_set_id = runtime.target_set.as_ref().ok_or_else(|| { 86 RadrootsRuntimeDistributionError::MissingTargetSet(runtime.id.clone()) 87 })?; 88 89 let adapter = self 90 .contract 91 .artifact_adapters 92 .get(&runtime.artifact_adapter) 93 .ok_or_else( 94 || RadrootsRuntimeDistributionError::UnknownArtifactAdapter { 95 runtime_id: runtime.id.clone(), 96 adapter_id: runtime.artifact_adapter.clone(), 97 }, 98 )?; 99 100 let (target_id, target) = 101 self.select_target(runtime, target_set_id, request.os, request.arch)?; 102 let archive_format_id = 103 self.resolve_archive_format_id(runtime, target_id, target, adapter)?; 104 let archive_format = self 105 .contract 106 .archive_formats 107 .get(&normalized_contract_key(archive_format_id)) 108 .ok_or_else(|| RadrootsRuntimeDistributionError::UnknownArchiveFormat { 109 target_id: target_id.to_string(), 110 archive_format_id: archive_format_id.to_string(), 111 })?; 112 113 let artifact_stem = format!("{}-{}-{}", runtime.release_unit, request.version, target_id); 114 let artifact_file_name = format!("{artifact_stem}{}", archive_format.extension); 115 116 Ok(ResolvedRuntimeArtifact { 117 runtime_id: runtime.id.clone(), 118 release_unit: runtime.release_unit.clone(), 119 package_name: runtime.package_name.clone(), 120 binary_name: runtime.binary_name.clone(), 121 artifact_adapter: runtime.artifact_adapter.clone(), 122 channel: channel.to_string(), 123 version: request.version.to_string(), 124 target_id: target_id.to_string(), 125 os: request.os.to_string(), 126 arch: request.arch.to_string(), 127 archive_format: archive_format_id.to_string(), 128 archive_extension: archive_format.extension.clone(), 129 artifact_stem, 130 artifact_file_name, 131 }) 132 } 133 134 fn ensure_channel_is_active( 135 &self, 136 channel: &str, 137 ) -> Result<(), RadrootsRuntimeDistributionError> { 138 if !self 139 .contract 140 .channels 141 .defined 142 .iter() 143 .any(|entry| entry == channel) 144 { 145 return Err(RadrootsRuntimeDistributionError::UnknownChannel( 146 channel.to_string(), 147 )); 148 } 149 if !self 150 .contract 151 .channels 152 .active 153 .iter() 154 .any(|entry| entry == channel) 155 { 156 return Err(RadrootsRuntimeDistributionError::InactiveChannel( 157 channel.to_string(), 158 )); 159 } 160 Ok(()) 161 } 162 163 fn select_target<'a>( 164 &'a self, 165 runtime: &RuntimeDistributionEntry, 166 target_set_id: &str, 167 os: &str, 168 arch: &str, 169 ) -> Result<(&'a str, &'a TargetSpec), RadrootsRuntimeDistributionError> { 170 let target_set = self 171 .contract 172 .target_sets 173 .get(target_set_id) 174 .ok_or_else(|| RadrootsRuntimeDistributionError::UnsupportedPlatform { 175 runtime_id: runtime.id.clone(), 176 os: os.to_string(), 177 arch: arch.to_string(), 178 })?; 179 180 let mut found_match = None; 181 for target_id in &target_set.targets { 182 let target = self.contract.targets.get(target_id).ok_or_else(|| { 183 RadrootsRuntimeDistributionError::UnknownTarget { 184 runtime_id: runtime.id.clone(), 185 target_set_id: target_set_id.to_string(), 186 target_id: target_id.clone(), 187 } 188 })?; 189 190 if target.os == os && target.arch == arch { 191 found_match = Some((target_id.as_str(), target)); 192 break; 193 } 194 } 195 196 found_match.ok_or_else(|| RadrootsRuntimeDistributionError::UnsupportedPlatform { 197 runtime_id: runtime.id.clone(), 198 os: os.to_string(), 199 arch: arch.to_string(), 200 }) 201 } 202 203 fn resolve_archive_format_id<'a>( 204 &self, 205 runtime: &RuntimeDistributionEntry, 206 target_id: &'a str, 207 target: &'a TargetSpec, 208 adapter: &'a ArtifactAdapter, 209 ) -> Result<&'a str, RadrootsRuntimeDistributionError> { 210 if let Some(format) = target.archive_format.as_deref() { 211 return Ok(format); 212 } 213 214 if adapter.supported_archive_formats.len() == 1 { 215 return Ok(adapter.supported_archive_formats[0].as_str()); 216 } 217 218 Err(RadrootsRuntimeDistributionError::MissingArchiveFormat { 219 runtime_id: runtime.id.clone(), 220 target_id: target_id.to_string(), 221 }) 222 } 223 } 224 225 fn normalized_contract_key(value: &str) -> String { 226 value.replace('.', "_") 227 }