Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / requirement.rs
6.9 kB 243 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2023 The Gleam contributors 3 4use std::fmt; 5use std::str::FromStr; 6 7use crate::Error; 8use crate::error::Result; 9use crate::io::make_relative; 10use camino::{Utf8Path, Utf8PathBuf}; 11use ecow::EcoString; 12use hexpm::version::Range; 13use serde::Deserialize; 14use serde::de::{self, Deserializer, MapAccess, Visitor}; 15use serde::ser::{Serialize, SerializeMap, Serializer}; 16 17#[derive(Deserialize, Debug, PartialEq, Eq, Clone)] 18#[serde(untagged, remote = "Self", deny_unknown_fields)] 19pub enum Requirement { 20 Hex { 21 #[serde(deserialize_with = "deserialise_range")] 22 version: Range, 23 }, 24 25 Path { 26 path: Utf8PathBuf, 27 }, 28 29 Git { 30 git: EcoString, 31 #[serde(rename = "ref")] 32 ref_: EcoString, 33 #[serde(default)] 34 path: Option<Utf8PathBuf>, 35 }, 36} 37 38impl Requirement { 39 pub fn hex(range: &str) -> Result<Requirement> { 40 Ok(Requirement::Hex { 41 version: Range::new(range.to_string()).map_err(|e| Error::InvalidVersionFormat { 42 input: range.to_string(), 43 error: e.to_string(), 44 })?, 45 }) 46 } 47 48 pub fn path(path: &str) -> Requirement { 49 Requirement::Path { path: path.into() } 50 } 51 52 pub fn git(url: &str, ref_: &str) -> Requirement { 53 Requirement::Git { 54 git: url.into(), 55 ref_: ref_.into(), 56 path: None, 57 } 58 } 59 60 pub fn git_with_path(url: &str, ref_: &str, path: &str) -> Requirement { 61 Requirement::Git { 62 git: url.into(), 63 ref_: ref_.into(), 64 path: Some(path.into()), 65 } 66 } 67 68 pub fn to_toml(&self, root_path: &Utf8Path) -> String { 69 match self { 70 Requirement::Hex { version: range } => { 71 format!(r#"{{ version = "{range}" }}"#) 72 } 73 Requirement::Path { path } => { 74 format!( 75 r#"{{ path = "{}" }}"#, 76 make_relative(root_path, path).as_str().replace('\\', "/") 77 ) 78 } 79 Requirement::Git { 80 git: url, 81 ref_, 82 path, 83 } => match path { 84 Some(path) => { 85 let path = path.as_str().replace('\\', "/"); 86 format!(r#"{{ git = "{url}", ref = "{ref_}", path = "{path}" }}"#) 87 } 88 None => format!(r#"{{ git = "{url}", ref = "{ref_}" }}"#), 89 }, 90 } 91 } 92} 93 94// Serialization 95 96impl Serialize for Requirement { 97 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 98 where 99 S: Serializer, 100 { 101 let mut map = serializer.serialize_map(Some(1))?; 102 match self { 103 Requirement::Hex { version: range } => map.serialize_entry("version", range)?, 104 Requirement::Path { path } => map.serialize_entry("path", path)?, 105 Requirement::Git { 106 git: url, 107 ref_, 108 path, 109 } => { 110 map.serialize_entry("git", url)?; 111 map.serialize_entry("ref", ref_)?; 112 if let Some(path) = path { 113 map.serialize_entry("path", path)?; 114 } 115 } 116 } 117 map.end() 118 } 119} 120 121// Deserialization 122 123fn deserialise_range<'de, D>(deserializer: D) -> Result<Range, D::Error> 124where 125 D: Deserializer<'de>, 126{ 127 let version = String::deserialize(deserializer)?; 128 Range::new(version).map_err(de::Error::custom) 129} 130 131#[derive(Debug, Copy, Clone)] 132pub struct Void; 133 134impl FromStr for Requirement { 135 type Err = Error; 136 137 fn from_str(s: &str) -> Result<Self, Self::Err> { 138 Requirement::hex(s) 139 } 140} 141 142struct RequirementVisitor; 143 144impl<'de> Visitor<'de> for RequirementVisitor { 145 type Value = Requirement; 146 147 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 148 formatter.write_str("string or map") 149 } 150 151 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> 152 where 153 E: de::Error, 154 { 155 match value.parse::<Requirement>() { 156 Ok(value) => Ok(value), 157 Err(error) => Err(de::Error::custom(error)), 158 } 159 } 160 161 fn visit_map<M>(self, visitor: M) -> Result<Self::Value, M::Error> 162 where 163 M: MapAccess<'de>, 164 { 165 Requirement::deserialize(de::value::MapAccessDeserializer::new(visitor)) 166 } 167} 168 169impl<'de> Deserialize<'de> for Requirement { 170 fn deserialize<D>(deserializer: D) -> Result<Requirement, D::Error> 171 where 172 D: Deserializer<'de>, 173 { 174 let requirement = deserializer.deserialize_any(RequirementVisitor)?; 175 if let Requirement::Git { 176 path: Some(path), .. 177 } = &requirement 178 { 179 if path.as_str().is_empty() { 180 return Err(de::Error::custom("git dependency path must not be empty")); 181 } 182 crate::io::validate_safe_relative_path(path).map_err(de::Error::custom)?; 183 } 184 Ok(requirement) 185 } 186} 187 188#[cfg(test)] 189mod tests { 190 191 use super::*; 192 use std::collections::HashMap; 193 194 #[test] 195 fn read_requirement() { 196 let toml = r#" 197 short = "~> 0.5" 198 hex = { version = "~> 1.0.0" } 199 local = { path = "/path/to/package" } 200 github = { git = "https://github.com/gleam-lang/otp.git", ref = "4d34935" } 201 "#; 202 let deps: HashMap<String, Requirement> = toml::from_str(toml).unwrap(); 203 assert_eq!(deps["short"], Requirement::hex("~> 0.5").unwrap()); 204 assert_eq!(deps["hex"], Requirement::hex("~> 1.0.0").unwrap()); 205 assert_eq!(deps["local"], Requirement::path("/path/to/package")); 206 assert_eq!( 207 deps["github"], 208 Requirement::git("https://github.com/gleam-lang/otp.git", "4d34935") 209 ); 210 } 211 212 #[test] 213 fn read_wrong_version() { 214 let toml = r#" 215 short = ">= 2.0 and < 3.0.0" 216 "#; 217 218 let error = 219 toml::from_str::<HashMap<String, Requirement>>(toml).expect_err("invalid version"); 220 insta::assert_snapshot!(error.to_string()); 221 } 222 223 #[test] 224 fn read_git_requirement_with_escaping_path() { 225 let toml = r#" 226 monorepo = { git = "https://github.com/gleam-lang/gleam.git", ref = "main", path = "../escape" } 227 "#; 228 229 let error = 230 toml::from_str::<HashMap<String, Requirement>>(toml).expect_err("escaping path"); 231 insta::assert_snapshot!(error.to_string()); 232 } 233 234 #[test] 235 fn read_git_requirement_with_empty_path() { 236 let toml = r#" 237 monorepo = { git = "https://github.com/gleam-lang/gleam.git", ref = "main", path = "" } 238 "#; 239 240 let error = toml::from_str::<HashMap<String, Requirement>>(toml).expect_err("empty path"); 241 insta::assert_snapshot!(error.to_string()); 242 } 243}