#![doc = include_str!("../.wiki/ManifestV2.md")]
#[cfg(feature = "downloads")]
use crate::version_manifest::VersionManifest;
#[cfg(feature = "downloads")]
use anyhow::Result;
use serde::{Deserialize, Serialize};
#[cfg(feature = "downloads")]
const PISTON_URL: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ManifestV2 {
pub latest: LatestManifest,
pub versions: Vec<Version>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct LatestManifest {
pub release: String,
pub snapshot: String,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Version {
pub id: String,
#[serde(rename = "type")]
pub release_type: ReleaseType,
pub url: String,
pub time: chrono::DateTime<chrono::Utc>,
#[serde(rename = "releaseTime")]
pub release_time: chrono::DateTime<chrono::Utc>,
pub sha1: String,
#[serde(rename = "complianceLevel")]
pub compliance_level: u8,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum ReleaseType {
#[serde(rename = "release")]
Release,
#[serde(rename = "snapshot")]
Snapshot,
#[serde(rename = "old_beta")]
OldBeta,
#[serde(rename = "old_alpha")]
OldAlpha,
}
impl std::fmt::Display for ReleaseType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ReleaseType::Release => write!(f, "Release"),
ReleaseType::Snapshot => write!(f, "Snapshot"),
ReleaseType::OldBeta => write!(f, "Old Beta"),
ReleaseType::OldAlpha => write!(f, "Old Alpha"),
}
}
}
impl ManifestV2 {
#[cfg(feature = "downloads")]
pub async fn fetch() -> Result<ManifestV2> {
debug!("Fetching versions manifest");
let manifest = reqwest::get(PISTON_URL).await?.json::<Self>().await?;
debug!("Found {} versions in manifest", manifest.versions.len());
Ok(manifest)
}
#[cfg(feature = "downloads")]
pub async fn version(&self, id: impl AsRef<str>) -> Result<Option<VersionManifest>> {
let id = id.as_ref();
match self.versions.iter().find(|version| version.id == id) {
Some(version) => Ok(Some(version.manifest().await?)),
None => Ok(None),
}
}
pub fn releases(&self) -> Vec<Version> {
self.versions.iter().filter(|version| version.release_type == ReleaseType::Release).cloned().collect()
}
}
#[cfg(feature = "downloads")]
impl Version {
pub async fn manifest(&self) -> Result<VersionManifest> {
debug!("Getting manifest version: {}", self.id);
VersionManifest::from_url(&self.url).await
}
}
#[cfg(test)]
#[cfg(feature = "downloads")]
mod test {
#[cfg(feature = "log")]
use crate::setup_logging;
#[tokio::test]
async fn fetch_manifest() {
use crate::manifest_v2::ManifestV2;
#[cfg(feature = "log")]
setup_logging();
let manifest = ManifestV2::fetch().await.unwrap();
assert!(!manifest.versions.is_empty());
}
#[tokio::test]
async fn version_manifest() {
use crate::manifest_v2::ManifestV2;
use futures_util::stream::{self, StreamExt};
#[cfg(feature = "log")]
setup_logging();
let manifest = ManifestV2::fetch().await.unwrap();
let results: Vec<_> =
stream::iter(manifest.versions).map(|version| async move { version.manifest().await }).buffer_unordered(64).collect().await;
for result in results {
assert!(result.is_ok());
}
}
}