Skip to main content

use_deno/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// Deno version metadata.
8#[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    /// Creates Deno version metadata.
17    ///
18    /// # Errors
19    ///
20    /// Returns [`DenoVersionParseError::InvalidVersion`] when the major version is zero.
21    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    /// Returns the major version.
38    #[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/// Error returned while parsing a Deno version.
53#[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/// Deno permission labels.
71#[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    /// Returns the permission label.
84    #[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/// Error returned while parsing Deno permissions.
126#[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/// Common Deno config file labels.
144#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
145pub enum DenoConfigFile {
146    DenoJson,
147    DenoJsonc,
148}
149
150impl DenoConfigFile {
151    /// Returns the file name label.
152    #[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}