#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::{fmt, str::FromStr};
use std::error::Error;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DenoVersion {
major: u16,
minor: Option<u16>,
patch: Option<u16>,
}
impl DenoVersion {
pub const fn new(
major: u16,
minor: Option<u16>,
patch: Option<u16>,
) -> Result<Self, DenoVersionParseError> {
if major == 0 || (minor.is_none() && patch.is_some()) {
Err(DenoVersionParseError::InvalidVersion)
} else {
Ok(Self {
major,
minor,
patch,
})
}
}
#[must_use]
pub const fn major(self) -> u16 {
self.major
}
}
impl FromStr for DenoVersion {
type Err = DenoVersionParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
parse_dotted_version(input)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DenoVersionParseError {
Empty,
InvalidVersion,
}
impl fmt::Display for DenoVersionParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("Deno version cannot be empty"),
Self::InvalidVersion => formatter.write_str("invalid Deno version"),
}
}
}
impl Error for DenoVersionParseError {}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DenoPermission {
Read,
Write,
Net,
Env,
Run,
Ffi,
Sys,
}
impl DenoPermission {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Read => "read",
Self::Write => "write",
Self::Net => "net",
Self::Env => "env",
Self::Run => "run",
Self::Ffi => "ffi",
Self::Sys => "sys",
}
}
}
impl fmt::Display for DenoPermission {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for DenoPermission {
type Err = DenoPermissionParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let trimmed = input.trim();
if trimmed.is_empty() {
return Err(DenoPermissionParseError::Empty);
}
match trimmed.to_ascii_lowercase().as_str() {
"read" => Ok(Self::Read),
"write" => Ok(Self::Write),
"net" => Ok(Self::Net),
"env" => Ok(Self::Env),
"run" => Ok(Self::Run),
"ffi" => Ok(Self::Ffi),
"sys" => Ok(Self::Sys),
_ => Err(DenoPermissionParseError::Unknown),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DenoPermissionParseError {
Empty,
Unknown,
}
impl fmt::Display for DenoPermissionParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("Deno permission cannot be empty"),
Self::Unknown => formatter.write_str("unknown Deno permission"),
}
}
}
impl Error for DenoPermissionParseError {}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DenoConfigFile {
DenoJson,
DenoJsonc,
}
impl DenoConfigFile {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::DenoJson => "deno.json",
Self::DenoJsonc => "deno.jsonc",
}
}
}
fn parse_dotted_version(input: &str) -> Result<DenoVersion, DenoVersionParseError> {
let trimmed = input.trim().trim_start_matches('v');
if trimmed.is_empty() {
return Err(DenoVersionParseError::Empty);
}
let parts = trimmed.split('.').collect::<Vec<_>>();
if parts.len() > 3 || parts.iter().any(|part| part.is_empty()) {
return Err(DenoVersionParseError::InvalidVersion);
}
let major = parse_part(parts[0])?;
let minor = parts.get(1).copied().map(parse_part).transpose()?;
let patch = parts.get(2).copied().map(parse_part).transpose()?;
DenoVersion::new(major, minor, patch)
}
fn parse_part(input: &str) -> Result<u16, DenoVersionParseError> {
input
.parse::<u16>()
.map_err(|_error| DenoVersionParseError::InvalidVersion)
}
#[cfg(test)]
mod tests {
use super::{DenoConfigFile, DenoPermission, DenoVersion};
#[test]
fn parses_deno_metadata() -> Result<(), Box<dyn std::error::Error>> {
let version: DenoVersion = "1.42.0".parse()?;
assert_eq!(version.major(), 1);
assert_eq!("net".parse::<DenoPermission>()?, DenoPermission::Net);
assert_eq!(DenoConfigFile::DenoJsonc.as_str(), "deno.jsonc");
Ok(())
}
}