Fork of daniellemaywood.uk/gleam — Wasm codegen work
4.9 kB
183 lines
1use std::fmt;
2use std::str::FromStr;
3
4use crate::Error;
5use crate::error::Result;
6use crate::io::make_relative;
7use camino::{Utf8Path, Utf8PathBuf};
8use ecow::EcoString;
9use hexpm::version::Range;
10use serde::Deserialize;
11use serde::de::{self, Deserializer, MapAccess, Visitor};
12use serde::ser::{Serialize, SerializeMap, Serializer};
13
14#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
15#[serde(untagged, remote = "Self", deny_unknown_fields)]
16pub enum Requirement {
17 Hex {
18 #[serde(deserialize_with = "deserialise_range")]
19 version: Range,
20 },
21
22 Path {
23 path: Utf8PathBuf,
24 },
25
26 Git {
27 git: EcoString,
28 #[serde(rename = "ref")]
29 ref_: EcoString,
30 },
31}
32
33impl Requirement {
34 pub fn hex(range: &str) -> Result<Requirement> {
35 Ok(Requirement::Hex {
36 version: Range::new(range.to_string()).map_err(|e| Error::InvalidVersionFormat {
37 input: range.to_string(),
38 error: e.to_string(),
39 })?,
40 })
41 }
42
43 pub fn path(path: &str) -> Requirement {
44 Requirement::Path { path: path.into() }
45 }
46
47 pub fn git(url: &str, ref_: &str) -> Requirement {
48 Requirement::Git {
49 git: url.into(),
50 ref_: ref_.into(),
51 }
52 }
53
54 pub fn to_toml(&self, root_path: &Utf8Path) -> String {
55 match self {
56 Requirement::Hex { version: range } => {
57 format!(r#"{{ version = "{range}" }}"#)
58 }
59 Requirement::Path { path } => {
60 format!(
61 r#"{{ path = "{}" }}"#,
62 make_relative(root_path, path).as_str().replace('\\', "/")
63 )
64 }
65 Requirement::Git { git: url, ref_ } => {
66 format!(r#"{{ git = "{url}", ref = "{ref_}" }}"#)
67 }
68 }
69 }
70}
71
72// Serialization
73
74impl Serialize for Requirement {
75 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76 where
77 S: Serializer,
78 {
79 let mut map = serializer.serialize_map(Some(1))?;
80 match self {
81 Requirement::Hex { version: range } => map.serialize_entry("version", range)?,
82 Requirement::Path { path } => map.serialize_entry("path", path)?,
83 Requirement::Git { git: url, ref_ } => {
84 map.serialize_entry("git", url)?;
85 map.serialize_entry("ref", ref_)?;
86 }
87 }
88 map.end()
89 }
90}
91
92// Deserialization
93
94fn deserialise_range<'de, D>(deserializer: D) -> Result<Range, D::Error>
95where
96 D: Deserializer<'de>,
97{
98 let version = String::deserialize(deserializer)?;
99 Range::new(version).map_err(de::Error::custom)
100}
101
102#[derive(Debug, Copy, Clone)]
103pub struct Void;
104
105impl FromStr for Requirement {
106 type Err = Error;
107
108 fn from_str(s: &str) -> Result<Self, Self::Err> {
109 Requirement::hex(s)
110 }
111}
112
113struct RequirementVisitor;
114
115impl<'de> Visitor<'de> for RequirementVisitor {
116 type Value = Requirement;
117
118 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119 formatter.write_str("string or map")
120 }
121
122 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
123 where
124 E: de::Error,
125 {
126 match value.parse::<Requirement>() {
127 Ok(value) => Ok(value),
128 Err(error) => Err(de::Error::custom(error)),
129 }
130 }
131
132 fn visit_map<M>(self, visitor: M) -> Result<Self::Value, M::Error>
133 where
134 M: MapAccess<'de>,
135 {
136 Requirement::deserialize(de::value::MapAccessDeserializer::new(visitor))
137 }
138}
139
140impl<'de> Deserialize<'de> for Requirement {
141 fn deserialize<D>(deserializer: D) -> Result<Requirement, D::Error>
142 where
143 D: Deserializer<'de>,
144 {
145 deserializer.deserialize_any(RequirementVisitor)
146 }
147}
148
149#[cfg(test)]
150mod tests {
151
152 use super::*;
153 use std::collections::HashMap;
154
155 #[test]
156 fn read_requirement() {
157 let toml = r#"
158 short = "~> 0.5"
159 hex = { version = "~> 1.0.0" }
160 local = { path = "/path/to/package" }
161 github = { git = "https://github.com/gleam-lang/otp.git", ref = "4d34935" }
162 "#;
163 let deps: HashMap<String, Requirement> = toml::from_str(toml).unwrap();
164 assert_eq!(deps["short"], Requirement::hex("~> 0.5").unwrap());
165 assert_eq!(deps["hex"], Requirement::hex("~> 1.0.0").unwrap());
166 assert_eq!(deps["local"], Requirement::path("/path/to/package"));
167 assert_eq!(
168 deps["github"],
169 Requirement::git("https://github.com/gleam-lang/otp.git", "4d34935")
170 );
171 }
172
173 #[test]
174 fn read_wrong_version() {
175 let toml = r#"
176 short = ">= 2.0 and < 3.0.0"
177 "#;
178
179 let error =
180 toml::from_str::<HashMap<String, Requirement>>(toml).expect_err("invalid version");
181 insta::assert_snapshot!(error.to_string());
182 }
183}