summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/assets.rs13
-rw-r--r--src/launcher.rs63
-rw-r--r--src/launcher/assets.rs10
-rw-r--r--src/launcher/runner.rs47
-rw-r--r--src/launcher/strsub.rs142
-rw-r--r--src/version/manifest.rs37
6 files changed, 229 insertions, 83 deletions
diff --git a/src/assets.rs b/src/assets.rs
index 9b0b7a7..f7d0977 100644
--- a/src/assets.rs
+++ b/src/assets.rs
@@ -1,3 +1,4 @@
+use std::collections::HashMap;
use std::fmt::Formatter;
use std::marker::PhantomData;
use serde::{Deserialize, Deserializer};
@@ -20,7 +21,7 @@ pub struct AssetIndex {
pub map_to_resources: bool,
#[serde(deserialize_with = "deserialize_assets")]
- pub objects: Vec<Asset>
+ pub objects: HashMap<String, Asset>
}
trait SetName {
@@ -33,7 +34,7 @@ impl SetName for Asset {
}
}
-fn deserialize_assets<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
+fn deserialize_assets<'de, D, T>(deserializer: D) -> Result<HashMap<String, T>, D::Error>
where
D: Deserializer<'de>,
T: SetName + Deserialize<'de>
@@ -44,7 +45,7 @@ where
where
T: SetName + Deserialize<'de>
{
- type Value = Vec<T>;
+ type Value = HashMap<String, T>;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("asset objects map")
@@ -54,11 +55,11 @@ where
where
A: MapAccess<'de>,
{
- let mut out = Vec::new();
+ let mut out = HashMap::new();
while let Some((key, mut asset)) = map.next_entry::<String, T>()? {
- asset.set_name(key);
- out.push(asset);
+ asset.set_name(key.clone());
+ out.insert(key, asset);
}
Ok(out)
diff --git a/src/launcher.rs b/src/launcher.rs
index 7611011..626bac9 100644
--- a/src/launcher.rs
+++ b/src/launcher.rs
@@ -6,18 +6,20 @@ mod rules;
mod assets;
mod extract;
mod settings;
+mod runner;
use std::borrow::Cow;
use std::cmp::min;
use std::env::consts::{ARCH, OS};
use std::error::Error;
-use std::ffi::OsStr;
+use std::ffi::{OsStr, OsString};
use std::fmt::{Display, Formatter};
use std::io::ErrorKind;
use std::io::ErrorKind::AlreadyExists;
use std::path::{Component, Path, PathBuf};
use std::{env, process};
use std::env::JoinPathsError;
+use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
use const_format::formatcp;
use futures::{StreamExt, TryStreamExt};
@@ -38,6 +40,8 @@ use assets::{AssetError, AssetRepository};
use crate::util::{self, FileVerifyError, IntegrityError};
pub use settings::*;
+use crate::assets::AssetIndex;
+use crate::version::manifest::VersionType;
#[derive(Debug)]
pub enum LogConfigError {
@@ -178,6 +182,22 @@ impl Error for LaunchError {
}
}
+pub struct Launch<'l> {
+ launcher: &'l Launcher,
+ asset_index_name: Option<String>,
+ classpath: String,
+ virtual_assets_path: Option<PathBuf>,
+ instance_home: PathBuf,
+ natives_path: PathBuf,
+ client_jar: Option<PathBuf>,
+ version_id: String,
+ version_type: Option<VersionType>,
+ asset_index: Option<AssetIndex>,
+
+ jvm_arguments: Vec<OsString>,
+ game_arguments: Vec<OsString>,
+}
+
impl Launcher {
// FIXME: more descriptive error type por favor
pub async fn new(home: impl AsRef<Path>, online: bool) -> Result<Launcher, Box<dyn Error>> {
@@ -330,13 +350,13 @@ impl Launcher {
self.ensure_file(&path, dlinfo).await?;
- Ok(strsub::replace_str(config.client.argument.as_str(), |key| match key {
- "path" => Some(path.to_string_lossy().clone()),
+ Ok(strsub::replace_string(config.client.argument.as_str(), |key| match key {
+ "path" => Some(path.to_string_lossy()),
_ => None
- }))
+ }).to_string())
}
- pub async fn prepare_launch(&self, version_id: &ProfileVersion, instance: &Instance) -> Result<(), LaunchError> {
+ pub async fn prepare_launch(&self, version_id: &ProfileVersion, instance: &Instance) -> Result<Launch<'_>, LaunchError> {
/* tasks 2 l;aunch the gayme!!!! :3
* - java runtime
* - normal process (good research, past figboot :3)
@@ -362,7 +382,7 @@ impl Launcher {
* - build argument list and whatnot also
*/
let Some(version_id) = self.versions.get_profile_version_id(version_id) else {
- // idk how common this usecase actually is
+ // idk how common this use case actually is
warn!("Can't use latest release/snapshot profiles while offline!");
return Err(LaunchError::UnknownVersion("<latest>".into()));
};
@@ -455,7 +475,7 @@ impl Launcher {
self.assets.ensure_assets(&asset_idx).await.map_err(|e| LaunchError::Assets(e))?;
- (Some(asset_idx_name), Some(asset_idx))
+ (asset_idx_name, Some(asset_idx))
} else {
(None, None)
};
@@ -486,9 +506,9 @@ impl Launcher {
info!("Extracting natives from libraries");
let natives_dir = self.libraries.extract_natives(extract_jobs).await?;
- let assets_root = if let Some(asset_idx) = asset_idx {
+ let game_assets = if let Some(asset_idx) = asset_idx.as_ref() {
info!("Reconstructing assets");
- self.assets.reconstruct_assets(&asset_idx, inst_home.as_path(), asset_idx_name.unwrap()).await
+ self.assets.reconstruct_assets(asset_idx, inst_home.as_path(), asset_idx_name).await
.map_err(|e| LaunchError::Assets(e))?
} else {
None
@@ -507,8 +527,21 @@ impl Launcher {
info!("Classpath: {classpath}");
- //todo!()
- Ok(())
+ Ok(Launch {
+ launcher: self,
+ asset_index_name: asset_idx_name.map(|s| s.to_owned()),
+ classpath,
+ virtual_assets_path: game_assets,
+ instance_home: inst_home,
+ natives_path: natives_dir,
+ client_jar: client_jar_path,
+ version_id: ver.id.to_string(),
+ version_type: ver.version_type.clone(),
+ asset_index: asset_idx,
+
+ jvm_arguments: Vec::new(), // TODO
+ game_arguments: Vec::new(), // TODO
+ })
}
}
@@ -563,14 +596,14 @@ impl LibraryRepository {
if let Some(classifier) = classifier {
match n.len() {
- 2 => Some(PathBuf::from(strsub::replace_thru(format!("{}-{}-{}.jar", n[0], n[1], classifier), Self::lib_replace))),
- 3 => Some(PathBuf::from(strsub::replace_thru(format!("{}-{}-{}-{}.jar", n[0], n[1], classifier, n[2]), Self::lib_replace))),
+ 2 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}.jar", n[0], n[1], classifier).as_str(), Self::lib_replace).as_ref())),
+ 3 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}-{}.jar", n[0], n[1], classifier, n[2]).as_str(), Self::lib_replace).as_ref())),
_ => None
}
} else {
match n.len() {
- 2 => Some(PathBuf::from(strsub::replace_thru(format!("{}-{}.jar", n[0], n[1]), Self::lib_replace))),
- 3 => Some(PathBuf::from(strsub::replace_thru(format!("{}-{}-{}.jar", n[0], n[1], n[2]), Self::lib_replace))),
+ 2 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}.jar", n[0], n[1]).as_str(), Self::lib_replace).as_ref())),
+ 3 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}.jar", n[0], n[1], n[2]).as_str(), Self::lib_replace).as_ref())),
_ => None
}
}
diff --git a/src/launcher/assets.rs b/src/launcher/assets.rs
index 992af2a..271f79e 100644
--- a/src/launcher/assets.rs
+++ b/src/launcher/assets.rs
@@ -93,6 +93,10 @@ impl AssetRepository {
home
})
}
+
+ pub fn get_home(&self) -> &Path {
+ self.home.as_path()
+ }
fn get_index_path(&self, id: &str) -> Result<PathBuf, AssetError> {
let mut indexes_path: PathBuf = [self.home.as_ref(), OsStr::new(INDEX_PATH)].iter().collect();
@@ -192,7 +196,7 @@ impl AssetRepository {
format!("{}{:02x}/{}", super::constants::URL_RESOURCE_BASE, obj.hash.bytes()[0], obj.hash)
}
- fn get_object_path(&self, obj: &Asset) -> PathBuf {
+ pub fn get_object_path(&self, obj: &Asset) -> PathBuf {
let hex_digest = obj.hash.to_string();
[self.home.as_ref(), OsStr::new(OBJECT_PATH), OsStr::new(&hex_digest[..2]), OsStr::new(&hex_digest)].iter().collect()
}
@@ -214,7 +218,7 @@ impl AssetRepository {
error: e
})?;
- for object in index.objects.iter() {
+ for object in index.objects.values() {
let path = self.get_object_path(object);
Self::ensure_dir(path.parent().unwrap()).await.map_err(|error| AssetError::IO { error, what: "creating directory for object" })?;
@@ -257,7 +261,7 @@ impl AssetRepository {
fs::create_dir_all(&target_path).await.map_err(|e| AssetError::from(("creating virtual assets directory", e)))?;
- stream::iter(index.objects.iter()
+ stream::iter(index.objects.values()
.map(|object| {
let obj_path = util::check_path(object.name.as_str()).map_err(AssetError::AssetNameError)?;
let obj_path = target_path.join(obj_path);
diff --git a/src/launcher/runner.rs b/src/launcher/runner.rs
new file mode 100644
index 0000000..8d12197
--- /dev/null
+++ b/src/launcher/runner.rs
@@ -0,0 +1,47 @@
+use std::borrow::Cow;
+use crate::launcher::Launch;
+use crate::launcher::strsub::SubFunc;
+
+impl<'k, 'rep, 'l: 'rep> SubFunc<'k, 'rep> for &'rep Launch<'l> {
+ fn substitute(self, key: &'k str) -> Option<Cow<'rep, str>> {
+ match key {
+ "assets_index_name" => self.asset_index_name.as_ref().map(|s| Cow::Borrowed(s.as_str())),
+ "assets_root" => Some(self.launcher.assets.get_home().to_string_lossy()),
+ "auth_access_token" => Some(Cow::Borrowed("-")), // TODO
+ "auth_player_name" => Some(Cow::Borrowed("Player")), // TODO
+ "auth_session" => Some(Cow::Borrowed("-")), // TODO
+ "auth_uuid" => Some(Cow::Borrowed("00000000-0000-0000-0000-000000000000")), // TODO
+ "auth_xuid" => Some(Cow::Borrowed("00000000-0000-0000-0000-000000000000")), // TODO
+ "classpath" => Some(Cow::Borrowed(self.classpath.as_str())), // TODO
+ "classpath_separator" => None, // FIXME
+ "game_assets" => self.virtual_assets_path.as_ref().map(|s| s.to_string_lossy()),
+ "game_directory" => Some(self.instance_home.to_string_lossy()),
+ "language" => Some(Cow::Borrowed("en-us")), // ???
+ "launcher_name" => Some(Cow::Borrowed("ozone (olauncher 3)")), // TODO
+ "launcher_version" => Some(Cow::Borrowed("yeah")), // TODO
+ "library_directory" => Some(self.launcher.libraries.home.to_string_lossy()),
+ "natives_directory" => Some(self.natives_path.to_string_lossy()),
+ "primary_jar" => self.client_jar.as_ref().map(|p| p.to_string_lossy()),
+ "quickPlayMultiplayer" => None, // TODO
+ "quickPlayPath" => None, // TODO
+ "quickPlayRealms" => None, // TODO
+ "quickPlaySingleplayer" => None, // TODO
+ "resolution_height" => None, // TODO
+ "resolution_width" => None, // TODO
+ "user_properties" => Some(Cow::Borrowed("{}")), // TODO
+ "user_property_map" => Some(Cow::Borrowed("[]")), // TODO
+ "user_type" => Some(Cow::Borrowed("legacy")), // TODO
+ "version_name" => Some(Cow::Borrowed(&self.version_id.as_ref())),
+ "version_type" => self.version_type.as_ref().map(|s| Cow::Borrowed(s.to_str())),
+ _ => {
+ if let Some(asset_key) = key.strip_prefix("asset=") {
+ return self.asset_index.as_ref()
+ .map_or(None, |idx| idx.objects.get(asset_key))
+ .map(|obj| Cow::Owned(self.launcher.assets.get_object_path(obj).to_string_lossy().into_owned()))
+ }
+
+ None
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/launcher/strsub.rs b/src/launcher/strsub.rs
index e01ef80..c89681e 100644
--- a/src/launcher/strsub.rs
+++ b/src/launcher/strsub.rs
@@ -8,39 +8,30 @@ const VAR_BEGIN: &str = "${";
const VAR_END: &str = "}";
const VAR_DEFAULT: &str = ":-";
-fn prev_char(slice: &str, idx: usize) -> Option<(usize, char)> {
- slice[..idx].char_indices().rev().next()
+pub trait SubFunc<'k, 'rep>: Copy {
+ fn substitute(self, key: &'k str) -> Option<Cow<'rep, str>>;
}
-pub trait SubFunc<'rep>: Fn(&str) -> Option<Cow<'rep, str>> {
- fn substitute(&self, key: &str) -> Option<Cow<'rep, str>>;
-}
-
-impl<'rep, F> SubFunc<'rep> for F
+impl<'k, 'rep, F> SubFunc<'k, 'rep> for F
where
- F: Fn(&str) -> Option<Cow<'rep, str>>
+ F: Fn(&'k str) -> Option<Cow<'rep, str>> + Copy
{
- fn substitute(&self, key: &str) -> Option<Cow<'rep, str>> {
+ fn substitute(self, key: &'k str) -> Option<Cow<'rep, str>> {
self(key)
}
}
-// basically the same thing as replace_string, but it creates the String itself and returns it.
-pub fn replace_str<'rep>(input: &str, sub: impl SubFunc<'rep>) -> String {
- let mut input = String::from(input);
- replace_string(&mut input, sub);
- input
-}
-
-pub fn replace_thru<'rep>(mut input: String, sub: impl SubFunc<'rep>) -> String {
- replace_string(&mut input, sub);
- input
-}
+/* NOTE: the in-place implementation has been replaced for the following reasons:
+ * - it was annoying to get lifetimes to work, so you could only either pass a trait implementation
+ * or a closure
+ * - it was probably slower than doing it out-of-place anyway, since you keep having to copy the
+ * tail of the string for each replacement
+ */
// handles ${replacements} on this string IN-PLACE. Calls the "sub" function for each key it receives.
// if "sub" returns None, it will use a default value or ignore the ${substitution}.
// There are no "invalid inputs" and this function should never panic unless "sub" panics.
-pub fn replace_string<'rep>(input: &mut String, sub: impl SubFunc<'rep>) {
+/*pub fn replace_string(input: &mut String, sub: impl SubFunc) {
let mut cursor = input.len();
while let Some(idx) = input[..cursor].rfind(VAR_BEGIN) {
// note: for some reason, apache processes escapes BEFORE checking if it's even a valid
@@ -76,6 +67,58 @@ pub fn replace_string<'rep>(input: &mut String, sub: impl SubFunc<'rep>) {
cursor = idx;
}
+}*/
+
+pub fn replace_string<'k, 'rep>(input: &'k str, sub: impl SubFunc<'k, 'rep>) -> Cow<'k, str> {
+ let mut ret: Option<String> = None;
+ let mut cursor = 0usize;
+
+ while let Some(idx) = input[cursor..].find(VAR_BEGIN) {
+ let idx = idx + cursor; // make idx an absolute index into 'input'
+ let spec_start = idx + VAR_BEGIN.len(); // the start of the "spec" (area inside {})
+
+ // first, check if this is escaped
+ if let Some((prev_idx, ESCAPE)) = input[..idx].char_indices().rev().next() {
+ let s = ret.get_or_insert_default();
+ s.push_str(&input[cursor..prev_idx]);
+
+ // advance past this so we don't match it again
+ s.push_str(&input[idx..spec_start]);
+ cursor = spec_start;
+ continue;
+ }
+
+ // now, find the closing tag
+ let Some(spec_end) = input[spec_start..].find(VAR_END).map(|v| v + spec_start) else {
+ break; // reached the end of the string
+ };
+
+ let full_spec = &input[spec_start..spec_end];
+
+ // check for a default argument
+ let (name, def) = if let Some(defidx) = full_spec.find(VAR_DEFAULT) {
+ (&full_spec[..defidx], Some(&full_spec[(defidx + VAR_DEFAULT.len())..]))
+ } else {
+ (full_spec, None)
+ };
+
+ let after = spec_end + VAR_END.len();
+ if let Some(subst) = sub.substitute(name).map_or_else(|| def.map(|d| Cow::Borrowed(d)), |v| Some(v)) {
+ let s = ret.get_or_insert_default();
+ s.push_str(&input[cursor..idx]);
+ s.push_str(subst.as_ref());
+ } else {
+ ret.get_or_insert_default().push_str(&input[cursor..after]);
+ }
+
+ cursor = after;
+ }
+
+ if let Some(ret) = ret.as_mut() {
+ ret.push_str(&input[cursor..]);
+ }
+
+ ret.map_or(Cow::Borrowed(input), Cow::Owned)
}
#[cfg(test)]
@@ -93,65 +136,62 @@ mod tests {
#[test]
fn test_standard_replace() {
- assert_eq!(replace_str("this has ${exists} and more", replace_fun), "this has value123 and more");
- assert_eq!(replace_str("multiple ${exists} repl${exists}ace", replace_fun), "multiple value123 replvalue123ace");
- assert_eq!(replace_str("${exists}${exists}", replace_fun), "value123value123");
+ assert_eq!(replace_string("this has ${exists} and more", replace_fun), "this has value123 and more");
+ assert_eq!(replace_string("multiple ${exists} repl${exists}ace", replace_fun), "multiple value123 replvalue123ace");
+ assert_eq!(replace_string("${exists}${exists}", replace_fun), "value123value123");
}
#[test]
fn test_empty_replace() {
- assert_eq!(replace_str("this has ${empty} and more", replace_fun), "this has ${empty} and more");
- assert_eq!(replace_str("multiple ${empty} repl${empty}ace", replace_fun), "multiple ${empty} repl${empty}ace");
- assert_eq!(replace_str("${empty}${empty}", replace_fun), "${empty}${empty}");
+ assert_eq!(replace_string("this has ${empty} and more", replace_fun), "this has ${empty} and more");
+ assert_eq!(replace_string("multiple ${empty} repl${empty}ace", replace_fun), "multiple ${empty} repl${empty}ace");
+ assert_eq!(replace_string("${empty}${empty}", replace_fun), "${empty}${empty}");
}
#[test]
fn test_homogenous_replace() {
- assert_eq!(replace_str("some ${exists} and ${empty} ...", replace_fun), "some value123 and ${empty} ...");
- assert_eq!(replace_str("some ${empty} and ${exists} ...", replace_fun), "some ${empty} and value123 ...");
- assert_eq!(replace_str("${exists}${empty}", replace_fun), "value123${empty}");
- assert_eq!(replace_str("${empty}${exists}", replace_fun), "${empty}value123");
+ assert_eq!(replace_string("some ${exists} and ${empty} ...", replace_fun), "some value123 and ${empty} ...");
+ assert_eq!(replace_string("some ${empty} and ${exists} ...", replace_fun), "some ${empty} and value123 ...");
+ assert_eq!(replace_string("${exists}${empty}", replace_fun), "value123${empty}");
+ assert_eq!(replace_string("${empty}${exists}", replace_fun), "${empty}value123");
}
#[test]
fn test_default_replace() {
- assert_eq!(replace_str("some ${exists:-def1} and ${empty:-def2} ...", replace_fun), "some value123 and def2 ...");
- assert_eq!(replace_str("some ${empty:-def1} and ${exists:-def2} ...", replace_fun), "some def1 and value123 ...");
- assert_eq!(replace_str("abc${empty:-}def", replace_fun), "abcdef");
- assert_eq!(replace_str("${empty:-}${empty:-}", replace_fun), "");
+ assert_eq!(replace_string("some ${exists:-def1} and ${empty:-def2} ...", replace_fun), "some value123 and def2 ...");
+ assert_eq!(replace_string("some ${empty:-def1} and ${exists:-def2} ...", replace_fun), "some def1 and value123 ...");
+ assert_eq!(replace_string("abc${empty:-}def", replace_fun), "abcdef");
+ assert_eq!(replace_string("${empty:-}${empty:-}", replace_fun), "");
}
#[test]
fn test_escape() {
- assert_eq!(replace_str("an $${escaped} replacement (${exists})", replace_fun), "an ${escaped} replacement (value123)");
- assert_eq!(replace_str("${exists}$${escaped}${exists}", replace_fun), "value123${escaped}value123");
+ assert_eq!(replace_string("an $${escaped} replacement (${exists})", replace_fun), "an ${escaped} replacement (value123)");
+ assert_eq!(replace_string("${exists}$${escaped}${exists}", replace_fun), "value123${escaped}value123");
// make sure this weird behavior is preserved... (the original code seemed to show it)
- assert_eq!(replace_str("some $${ else", replace_fun), "some ${ else");
+ assert_eq!(replace_string("some $${ else", replace_fun), "some ${ else");
}
#[test]
fn test_weird() {
- assert_eq!(replace_str("${exists}", replace_fun), "value123");
- assert_eq!(replace_str("$${empty}", replace_fun), "${empty}");
- assert_eq!(replace_str("${empty:-a}", replace_fun), "a");
- assert_eq!(replace_str("${empty:-}", replace_fun), "");
-
- // there is no nested evaluation, but the algorithm does proceed through the string backwards
- assert_eq!(replace_str("${exists:-${exists}}", replace_fun), "${exists:-value123}");
+ assert_eq!(replace_string("${exists}", replace_fun), "value123");
+ assert_eq!(replace_string("$${empty}", replace_fun), "${empty}");
+ assert_eq!(replace_string("${empty:-a}", replace_fun), "a");
+ assert_eq!(replace_string("${empty:-}", replace_fun), "");
}
// these make sure it doesn't chop up multibyte characters illegally
#[test]
fn test_multibyte_surround() {
- assert_eq!(replace_str("\u{1f354}$${}\u{1f354}", replace_fun), "\u{1f354}${}\u{1f354}");
- assert_eq!(replace_str("\u{1f354}${exists}\u{1f354}${empty:-}\u{1f354}", replace_fun), "\u{1f354}value123\u{1f354}\u{1f354}");
+ assert_eq!(replace_string("\u{1f354}$${}\u{1f354}", replace_fun), "\u{1f354}${}\u{1f354}");
+ assert_eq!(replace_string("\u{1f354}${exists}\u{1f354}${empty:-}\u{1f354}", replace_fun), "\u{1f354}value123\u{1f354}\u{1f354}");
}
#[test]
fn test_multibyte_replace() {
- assert_eq!(replace_str("borger ${borger}", replace_fun), "borger \u{1f354}");
- assert_eq!(replace_str("${exists:-\u{1f354}}${empty:-\u{1f354}}", replace_fun), "value123\u{1f354}");
- assert_eq!(replace_str("${borger}$${}${borger}", replace_fun), "\u{1f354}${}\u{1f354}");
+ assert_eq!(replace_string("borger ${borger}", replace_fun), "borger \u{1f354}");
+ assert_eq!(replace_string("${exists:-\u{1f354}}${empty:-\u{1f354}}", replace_fun), "value123\u{1f354}");
+ assert_eq!(replace_string("${borger}$${}${borger}", replace_fun), "\u{1f354}${}\u{1f354}");
}
-} \ No newline at end of file
+}
diff --git a/src/version/manifest.rs b/src/version/manifest.rs
index c8c49b8..18653f3 100644
--- a/src/version/manifest.rs
+++ b/src/version/manifest.rs
@@ -1,5 +1,6 @@
use core::fmt;
-
+use std::convert::Infallible;
+use std::str::FromStr;
use chrono::{DateTime, Utc};
use serde::{de::Visitor, Deserialize};
use sha1_smol::Digest;
@@ -19,6 +20,32 @@ pub enum VersionType {
Other(String)
}
+impl FromStr for VersionType {
+ type Err = Infallible;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "snapshot" => Ok(Self::Snapshot),
+ "release" => Ok(Self::Release),
+ "old_beta" => Ok(Self::OldBeta),
+ "old_alpha" => Ok(Self::OldAlpha),
+ _ => Ok(Self::Other(s.to_owned()))
+ }
+ }
+}
+
+impl VersionType {
+ pub fn to_str(&self) -> &str {
+ match self {
+ Self::Snapshot => "snapshot",
+ Self::Release => "release",
+ Self::OldBeta => "old_beta",
+ Self::OldAlpha => "old_alpha",
+ Self::Other(s) => s
+ }
+ }
+}
+
struct VersionTypeVisitor;
impl<'de> Visitor<'de> for VersionTypeVisitor {
@@ -31,13 +58,7 @@ impl<'de> Visitor<'de> for VersionTypeVisitor {
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error, {
- match v {
- "snapshot" => Ok(VersionType::Snapshot),
- "release" => Ok(VersionType::Release),
- "old_beta" => Ok(VersionType::OldBeta),
- "old_alpha" => Ok(VersionType::OldAlpha),
- _ => Ok(VersionType::Other(v.to_owned()))
- }
+ Ok(VersionType::from_str(v).unwrap(/* infallible */))
}
}