From e03e8adf1f83419b66e8d0d20545609e817136b0 Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 6 Apr 2021 17:39:59 +0200 Subject: [PATCH] Add versions and items api Signed-off-by: trivernis --- .gitignore | 2 ++ .gitmodules | 3 ++ .idea/.gitignore | 8 +++++ .idea/csv-plugin.xml | 16 ++++++++++ .idea/discord.xml | 6 ++++ .idea/minecraft-data-rs.iml | 11 +++++++ .idea/modules.xml | 8 +++++ .idea/vcs.xml | 6 ++++ Cargo.toml | 14 +++++++++ minecraft-data | 1 + src/api/items.rs | 39 ++++++++++++++++++++++++ src/api/mod.rs | 24 +++++++++++++++ src/api/tests/items.rs | 11 +++++++ src/api/tests/mod.rs | 19 ++++++++++++ src/api/tests/versions.rs | 20 ++++++++++++ src/api/versions.rs | 40 ++++++++++++++++++++++++ src/data/mod.rs | 61 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 13 ++++++++ src/models/item.rs | 15 +++++++++ src/models/mod.rs | 2 ++ src/models/version.rs | 28 +++++++++++++++++ src/utils/error.rs | 19 ++++++++++++ src/utils/mod.rs | 1 + 23 files changed, 367 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .idea/.gitignore create mode 100644 .idea/csv-plugin.xml create mode 100644 .idea/discord.xml create mode 100644 .idea/minecraft-data-rs.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.toml create mode 160000 minecraft-data create mode 100644 src/api/items.rs create mode 100644 src/api/mod.rs create mode 100644 src/api/tests/items.rs create mode 100644 src/api/tests/mod.rs create mode 100644 src/api/tests/versions.rs create mode 100644 src/api/versions.rs create mode 100644 src/data/mod.rs create mode 100644 src/lib.rs create mode 100644 src/models/item.rs create mode 100644 src/models/mod.rs create mode 100644 src/models/version.rs create mode 100644 src/utils/error.rs create mode 100644 src/utils/mod.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..acac9a5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "minecraft-data"] + path = minecraft-data + url = git@github.com:PrismarineJS/minecraft-data.git diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/csv-plugin.xml b/.idea/csv-plugin.xml new file mode 100644 index 0000000..2f7083e --- /dev/null +++ b/.idea/csv-plugin.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..cd711a0 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/minecraft-data-rs.iml b/.idea/minecraft-data-rs.iml new file mode 100644 index 0000000..c254557 --- /dev/null +++ b/.idea/minecraft-data-rs.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b086264 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4c6eb1b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "minecraft-data-rs" +version = "0.1.0" +authors = ["trivernis "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +thiserror = "1.0.24" +serde_json = "1.0.64" +serde_derive = "1.0.125" +serde = "1.0.125" +include_dir = "0.6.0" diff --git a/minecraft-data b/minecraft-data new file mode 160000 index 0000000..920d361 --- /dev/null +++ b/minecraft-data @@ -0,0 +1 @@ +Subproject commit 920d361e13351bbbd812c3bbd0ed05f4194b05fe diff --git a/src/api/items.rs b/src/api/items.rs new file mode 100644 index 0000000..ab582ee --- /dev/null +++ b/src/api/items.rs @@ -0,0 +1,39 @@ +use crate::data::{get_version_specific_file, ITEMS_FILE}; +use crate::models::item::Item; +use crate::models::version::Version; +use crate::{DataError, DataResult}; +use std::collections::HashMap; +use std::iter::FromIterator; +use std::sync::Arc; + +/// API to access item information +pub struct Items { + version: Arc, +} + +impl Items { + pub fn new(version: Arc) -> Self { + Self { version } + } + + /// Returns the items + pub fn items_array(&self) -> DataResult> { + let content = get_version_specific_file(&self.version, ITEMS_FILE)?; + + serde_json::from_str::>(&*content).map_err(DataError::from) + } + + /// Returns the items indexed by name + pub fn items_by_name(&self) -> DataResult> { + Ok(HashMap::from_iter( + self.items_array()?.into_iter().map(|i| (i.name.clone(), i)), + )) + } + + /// Returns the items indexed by ID + pub fn items(&self) -> DataResult> { + Ok(HashMap::from_iter( + self.items_array()?.into_iter().map(|i| (i.id.clone(), i)), + )) + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..a6a670a --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,24 @@ +use crate::api::items::Items; +use crate::models::version::Version; +use std::sync::Arc; + +#[cfg(test)] +mod tests; + +pub mod items; +pub mod versions; + +pub struct Api { + version: Arc, + items: Items, +} + +impl Api { + pub fn new(version: Version) -> Self { + let version = Arc::new(version); + Self { + version: Arc::clone(&version), + items: Items::new(Arc::clone(&version)), + } + } +} diff --git a/src/api/tests/items.rs b/src/api/tests/items.rs new file mode 100644 index 0000000..a07c978 --- /dev/null +++ b/src/api/tests/items.rs @@ -0,0 +1,11 @@ +use crate::api::tests::{get_api, get_test_versions}; + +#[test] +pub fn test_items_array() { + let versions = get_test_versions(); + + for version in versions { + let api = get_api(version); + assert_ne!(api.items.items_array().unwrap().len(), 0) + } +} diff --git a/src/api/tests/mod.rs b/src/api/tests/mod.rs new file mode 100644 index 0000000..c1c0aa2 --- /dev/null +++ b/src/api/tests/mod.rs @@ -0,0 +1,19 @@ +use crate::api::versions::{available_versions, versions}; +use crate::api::Api; +use crate::models::version::Version; + +mod items; +mod versions; + +fn get_api(version: Version) -> Api { + Api::new(version) +} + +fn get_test_versions() -> Vec { + let available = available_versions().unwrap(); + versions() + .unwrap() + .into_iter() + .filter(|v| available.contains(&v.minecraft_version)) + .collect() +} diff --git a/src/api/tests/versions.rs b/src/api/tests/versions.rs new file mode 100644 index 0000000..023f753 --- /dev/null +++ b/src/api/tests/versions.rs @@ -0,0 +1,20 @@ +use crate::api::versions::{latest_stable, versions, versions_by_minecraft_version}; + +#[test] +fn test_versions() { + let unordered_versions = versions().unwrap(); + assert_ne!(unordered_versions.len(), 0) +} + +#[test] +fn test_versions_by_minecraft_version() { + let versions = versions_by_minecraft_version().unwrap(); + assert!(versions.get("1.16").is_some()); + assert!(versions.get("1.14.12").is_none()); + assert_eq!(versions.get("1.16.3").unwrap().major_version, "1.16") +} + +#[test] +fn test_latest_stable_version() { + assert!(latest_stable().is_ok()) +} diff --git a/src/api/versions.rs b/src/api/versions.rs new file mode 100644 index 0000000..76b483a --- /dev/null +++ b/src/api/versions.rs @@ -0,0 +1,40 @@ +use crate::data::{get_common_file, PROTOCOL_VERSIONS_FILE, VERSIONS_FILE}; +use crate::models::version::Version; +use crate::{DataError, DataResult}; +use std::collections::HashMap; +use std::iter::FromIterator; + +/// Returns the unsorted list of versions +pub fn versions() -> DataResult> { + let content = get_common_file(PROTOCOL_VERSIONS_FILE)?; + let versions = serde_json::from_str::>(&*content)?; + + Ok(versions) +} + +/// Returns the versions indexed by minecraft version +pub fn versions_by_minecraft_version() -> DataResult> { + let indexed_versions = HashMap::from_iter( + versions()? + .into_iter() + .map(|v| (v.minecraft_version.clone(), v)), + ); + + Ok(indexed_versions) +} + +/// Returns the latest stable version (hardcoded at the moment) +pub fn latest_stable() -> DataResult { + versions_by_minecraft_version()? + .get("1.16.5") + .cloned() + .ok_or(DataError::NotFoundError("1.16.5".to_string())) +} + +/// Returns a list of available version information +pub(crate) fn available_versions() -> DataResult> { + Ok(get_common_file(VERSIONS_FILE)? + .split_whitespace() + .map(|s| s.to_string()) + .collect()) +} diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..ad5e603 --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1,61 @@ +use crate::models::version::Version; +use crate::{DataError, DataResult}; +use include_dir::Dir; + +pub static MINECRAFT_DATA: Dir = include_dir::include_dir!("minecraft-data/data/pc"); + +pub static BIOMES_FILE: &str = "biomes.json"; +pub static BLOCK_LOOT_FILE: &str = "blockLoot.json"; +pub static BLOCKS_FILE: &str = "blocks.json"; +pub static COMMANDS_FILE: &str = "commands.json"; +pub static ENTITIES_FILE: &str = "entities.json"; +pub static ENTITY_LOOT_FILE: &str = "entityLoot.json"; +pub static ITEMS_FILE: &str = "items.json"; +pub static LOGIN_PACKET_FILE: &str = "loginPacket.json"; +pub static MATERIALS_FILE: &str = "materials.json"; +pub static PROTOCOL_FILE: &str = "protocol.json"; +pub static RECIPES_FILE: &str = "recipes.json"; +pub static TINTS_FILE: &str = "tints.json"; +// pub static VERSION_FILE: &str = "version.json"; +pub static MAP_ICONS_FILE: &str = "mapIcons.json"; +pub static PARTICLES_FILE: &str = "particles.json"; +pub static PROTOCOL_VERSIONS_FILE: &str = "protocolVersions.json"; +pub static VERSIONS_FILE: &str = "versions.json"; + +/// Returns the string encoded content of the common file +pub fn get_common_file(filename: &str) -> DataResult { + MINECRAFT_DATA + .get_file(format!("common/{}", filename)) + .ok_or(DataError::NotFoundError(filename.to_string()))? + .contents_utf8() + .ok_or(DataError::InvalidEncodingError(filename.to_string())) + .map(|d| d.to_string()) +} + +/// Returns the string encoded content of the version specific file +pub fn get_version_specific_file(version: &Version, filename: &str) -> DataResult { + let search_folders = vec![ + version.minecraft_version.clone(), + version.major_version.clone(), + version.major_first(), + version.previous_major(), + version.previous_major_first(), + ]; + println!("{:?}", search_folders); + let mut data = None; + + for folder in search_folders { + data = MINECRAFT_DATA.get_file(format!("{}/{}", folder, filename)); + if data.is_some() { + break; + } + } + + data.ok_or(DataError::NotFoundError(format!( + "{}/{}", + version.minecraft_version, filename + )))? + .contents_utf8() + .ok_or(DataError::InvalidEncodingError(filename.to_string())) + .map(|d| d.to_string()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5eea4f4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,13 @@ +#[macro_use] +extern crate serde_derive; + +#[macro_use] +extern crate include_dir; + +pub mod api; +pub(crate) mod data; +pub mod models; +pub(crate) mod utils; + +pub use utils::error::DataError; +pub use utils::error::DataResult; diff --git a/src/models/item.rs b/src/models/item.rs new file mode 100644 index 0000000..933cde0 --- /dev/null +++ b/src/models/item.rs @@ -0,0 +1,15 @@ +use serde_json::Value; + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] +pub struct Item { + pub id: u32, + pub display_name: String, + pub stack_size: u8, + pub enchant_categories: Option>, + pub fixed_with: Option>, + pub max_durability: Option, + pub name: String, + pub variations: Option, + pub durability: Option, +} diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..afbce8b --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,2 @@ +pub mod item; +pub mod version; diff --git a/src/models/version.rs b/src/models/version.rs new file mode 100644 index 0000000..a80aab2 --- /dev/null +++ b/src/models/version.rs @@ -0,0 +1,28 @@ +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all(deserialize = "camelCase", serialize = "snake_case"))] +pub struct Version { + pub version: i32, + pub minecraft_version: String, + pub major_version: String, +} + +impl Version { + /// Returns the first version of the current major version + pub(crate) fn major_first(&self) -> String { + format!("{}.1", self.major_version) + } + + /// Returns the previous major version + pub(crate) fn previous_major(&self) -> String { + let major = self.major_version.split('.').last().unwrap(); + let major_num = major.parse::().unwrap(); + + self.major_version + .replace(major, format!("{}", major_num - 1).as_str()) + } + + /// Returns the first version of the previous major version + pub(crate) fn previous_major_first(&self) -> String { + format!("{}.1", self.previous_major()) + } +} diff --git a/src/utils/error.rs b/src/utils/error.rs new file mode 100644 index 0000000..4735202 --- /dev/null +++ b/src/utils/error.rs @@ -0,0 +1,19 @@ +use std::io; +use thiserror::Error; + +pub type DataResult = Result; + +#[derive(Error, Debug)] +pub enum DataError { + #[error("IO Error: {0}")] + IOError(#[from] io::Error), + + #[error("JSON Error: {0}")] + JsonError(#[from] serde_json::Error), + + #[error("Object {0} not found")] + NotFoundError(String), + + #[error("Invalid encoding of file {0}")] + InvalidEncodingError(String), +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..a91e735 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod error;