1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct DenoVersion {
10 major: u16,
11 minor: Option<u16>,
12 patch: Option<u16>,
13}
14
15impl DenoVersion {
16 pub const fn new(
22 major: u16,
23 minor: Option<u16>,
24 patch: Option<u16>,
25 ) -> Result<Self, DenoVersionParseError> {
26 if major == 0 || (minor.is_none() && patch.is_some()) {
27 Err(DenoVersionParseError::InvalidVersion)
28 } else {
29 Ok(Self {
30 major,
31 minor,
32 patch,
33 })
34 }
35 }
36
37 #[must_use]
39 pub const fn major(self) -> u16 {
40 self.major
41 }
42}
43
44impl FromStr for DenoVersion {
45 type Err = DenoVersionParseError;
46
47 fn from_str(input: &str) -> Result<Self, Self::Err> {
48 parse_dotted_version(input)
49 }
50}
51
52#[derive(Clone, Copy, Debug, Eq, PartialEq)]
54pub enum DenoVersionParseError {
55 Empty,
56 InvalidVersion,
57}
58
59impl fmt::Display for DenoVersionParseError {
60 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::Empty => formatter.write_str("Deno version cannot be empty"),
63 Self::InvalidVersion => formatter.write_str("invalid Deno version"),
64 }
65 }
66}
67
68impl Error for DenoVersionParseError {}
69
70#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
72pub enum DenoPermission {
73 Read,
74 Write,
75 Net,
76 Env,
77 Run,
78 Ffi,
79 Sys,
80}
81
82impl DenoPermission {
83 #[must_use]
85 pub const fn as_str(self) -> &'static str {
86 match self {
87 Self::Read => "read",
88 Self::Write => "write",
89 Self::Net => "net",
90 Self::Env => "env",
91 Self::Run => "run",
92 Self::Ffi => "ffi",
93 Self::Sys => "sys",
94 }
95 }
96}
97
98impl fmt::Display for DenoPermission {
99 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
100 formatter.write_str(self.as_str())
101 }
102}
103
104impl FromStr for DenoPermission {
105 type Err = DenoPermissionParseError;
106
107 fn from_str(input: &str) -> Result<Self, Self::Err> {
108 let trimmed = input.trim();
109 if trimmed.is_empty() {
110 return Err(DenoPermissionParseError::Empty);
111 }
112 match trimmed.to_ascii_lowercase().as_str() {
113 "read" => Ok(Self::Read),
114 "write" => Ok(Self::Write),
115 "net" => Ok(Self::Net),
116 "env" => Ok(Self::Env),
117 "run" => Ok(Self::Run),
118 "ffi" => Ok(Self::Ffi),
119 "sys" => Ok(Self::Sys),
120 _ => Err(DenoPermissionParseError::Unknown),
121 }
122 }
123}
124
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
127pub enum DenoPermissionParseError {
128 Empty,
129 Unknown,
130}
131
132impl fmt::Display for DenoPermissionParseError {
133 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
134 match self {
135 Self::Empty => formatter.write_str("Deno permission cannot be empty"),
136 Self::Unknown => formatter.write_str("unknown Deno permission"),
137 }
138 }
139}
140
141impl Error for DenoPermissionParseError {}
142
143#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
145pub enum DenoConfigFile {
146 DenoJson,
147 DenoJsonc,
148}
149
150impl DenoConfigFile {
151 #[must_use]
153 pub const fn as_str(self) -> &'static str {
154 match self {
155 Self::DenoJson => "deno.json",
156 Self::DenoJsonc => "deno.jsonc",
157 }
158 }
159}
160
161fn parse_dotted_version(input: &str) -> Result<DenoVersion, DenoVersionParseError> {
162 let trimmed = input.trim().trim_start_matches('v');
163 if trimmed.is_empty() {
164 return Err(DenoVersionParseError::Empty);
165 }
166 let parts = trimmed.split('.').collect::<Vec<_>>();
167 if parts.len() > 3 || parts.iter().any(|part| part.is_empty()) {
168 return Err(DenoVersionParseError::InvalidVersion);
169 }
170 let major = parse_part(parts[0])?;
171 let minor = parts.get(1).copied().map(parse_part).transpose()?;
172 let patch = parts.get(2).copied().map(parse_part).transpose()?;
173 DenoVersion::new(major, minor, patch)
174}
175
176fn parse_part(input: &str) -> Result<u16, DenoVersionParseError> {
177 input
178 .parse::<u16>()
179 .map_err(|_error| DenoVersionParseError::InvalidVersion)
180}
181
182#[cfg(test)]
183mod tests {
184 use super::{DenoConfigFile, DenoPermission, DenoVersion};
185
186 #[test]
187 fn parses_deno_metadata() -> Result<(), Box<dyn std::error::Error>> {
188 let version: DenoVersion = "1.42.0".parse()?;
189 assert_eq!(version.major(), 1);
190 assert_eq!("net".parse::<DenoPermission>()?, DenoPermission::Net);
191 assert_eq!(DenoConfigFile::DenoJsonc.as_str(), "deno.jsonc");
192 Ok(())
193 }
194}