Fork of daniellemaywood.uk/gleam — Wasm codegen work
5.7 kB
211 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 format!(r#"{{ git = "{url}", ref = "{ref_}", path = "{path}" }}"#)
86 }
87 None => format!(r#"{{ git = "{url}", ref = "{ref_}" }}"#),
88 },
89 }
90 }
91}
92
93// Serialization
94
95impl Serialize for Requirement {
96 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
97 where
98 S: Serializer,
99 {
100 let mut map = serializer.serialize_map(Some(1))?;
101 match self {
102 Requirement::Hex { version: range } => map.serialize_entry("version", range)?,
103 Requirement::Path { path } => map.serialize_entry("path", path)?,
104 Requirement::Git {
105 git: url,
106 ref_,
107 path,
108 } => {
109 map.serialize_entry("git", url)?;
110 map.serialize_entry("ref", ref_)?;
111 if let Some(path) = path {
112 map.serialize_entry("path", path)?;
113 }
114 }
115 }
116 map.end()
117 }
118}
119
120// Deserialization
121
122fn deserialise_range<'de, D>(deserializer: D) -> Result<Range, D::Error>
123where
124 D: Deserializer<'de>,
125{
126 let version = String::deserialize(deserializer)?;
127 Range::new(version).map_err(de::Error::custom)
128}
129
130#[derive(Debug, Copy, Clone)]
131pub struct Void;
132
133impl FromStr for Requirement {
134 type Err = Error;
135
136 fn from_str(s: &str) -> Result<Self, Self::Err> {
137 Requirement::hex(s)
138 }
139}
140
141struct RequirementVisitor;
142
143impl<'de> Visitor<'de> for RequirementVisitor {
144 type Value = Requirement;
145
146 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
147 formatter.write_str("string or map")
148 }
149
150 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
151 where
152 E: de::Error,
153 {
154 match value.parse::<Requirement>() {
155 Ok(value) => Ok(value),
156 Err(error) => Err(de::Error::custom(error)),
157 }
158 }
159
160 fn visit_map<M>(self, visitor: M) -> Result<Self::Value, M::Error>
161 where
162 M: MapAccess<'de>,
163 {
164 Requirement::deserialize(de::value::MapAccessDeserializer::new(visitor))
165 }
166}
167
168impl<'de> Deserialize<'de> for Requirement {
169 fn deserialize<D>(deserializer: D) -> Result<Requirement, D::Error>
170 where
171 D: Deserializer<'de>,
172 {
173 deserializer.deserialize_any(RequirementVisitor)
174 }
175}
176
177#[cfg(test)]
178mod tests {
179
180 use super::*;
181 use std::collections::HashMap;
182
183 #[test]
184 fn read_requirement() {
185 let toml = r#"
186 short = "~> 0.5"
187 hex = { version = "~> 1.0.0" }
188 local = { path = "/path/to/package" }
189 github = { git = "https://github.com/gleam-lang/otp.git", ref = "4d34935" }
190 "#;
191 let deps: HashMap<String, Requirement> = toml::from_str(toml).unwrap();
192 assert_eq!(deps["short"], Requirement::hex("~> 0.5").unwrap());
193 assert_eq!(deps["hex"], Requirement::hex("~> 1.0.0").unwrap());
194 assert_eq!(deps["local"], Requirement::path("/path/to/package"));
195 assert_eq!(
196 deps["github"],
197 Requirement::git("https://github.com/gleam-lang/otp.git", "4d34935")
198 );
199 }
200
201 #[test]
202 fn read_wrong_version() {
203 let toml = r#"
204 short = ">= 2.0 and < 3.0.0"
205 "#;
206
207 let error =
208 toml::from_str::<HashMap<String, Requirement>>(toml).expect_err("invalid version");
209 insta::assert_snapshot!(error.to_string());
210 }
211}