terra-wld 0.3.0

Terraria world parser
Documentation
use core::fmt;

use serde::{
	Deserialize, Deserializer, Serialize, Serializer,
	de::{self, MapAccess, SeqAccess, Visitor},
	ser::SerializeStruct,
};
use time::Duration;

#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "PascalCase")]
pub struct WorldManifest {
	#[serde(rename = "GenPassResults")]
	pub generator_passes: Vec<GeneratorPass>,
	#[serde(deserialize_with = "non_empty_str")]
	pub version: Option<String>,
	#[serde(deserialize_with = "non_empty_str", rename = "GitSHA")]
	pub git_sha: Option<String>,
}

fn non_empty_str<'de, D: Deserializer<'de>>(d: D) -> Result<Option<String>, D::Error> {
	use serde::Deserialize;
	let o: Option<String> = Option::deserialize(d)?;
	Ok(o.filter(|s| !s.is_empty()))
}

impl Serialize for WorldManifest {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer,
	{
		let mut world_manifest = serializer.serialize_struct("WorldManifest", 4)?;
		world_manifest.serialize_field("GenPassResults", &self.generator_passes)?;
		world_manifest.serialize_field("Version", &self.version)?;
		world_manifest.serialize_field("GitSHA", &self.git_sha.as_deref().unwrap_or_default())?;
		world_manifest.serialize_field(
			"FinalHash",
			&self.generator_passes.last().and_then(|x| match x.result {
				GeneratorPassResult::Result {
					duration: _,
					random: _,
					hash,
				} => hash,
				GeneratorPassResult::Skipped => None,
			}),
		)?;
		world_manifest.end()
	}
}

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "PascalCase")]
pub struct GeneratorPass {
	pub name: String,
	#[serde(flatten)]
	pub result: GeneratorPassResult,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum GeneratorPassResult {
	Skipped,
	Result {
		duration: Duration,
		random: i32,
		hash: Option<u32>,
	},
}

impl Serialize for GeneratorPassResult {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer,
	{
		let mut duration = Duration::ZERO;
		let mut random = 0;
		let mut hash = None;
		let mut is_skipped = true;

		match *self {
			GeneratorPassResult::Result {
				duration: d,
				random: r,
				hash: h,
			} => {
				duration = d;
				random = r;
				hash = h;
				is_skipped = false;
			}

			GeneratorPassResult::Skipped => {}
		}

		let mut gen_pass_result = serializer.serialize_struct("GenPassResults", 5)?;
		gen_pass_result.serialize_field(
			"DurationMs",
			&(duration
				.whole_milliseconds()
				.clamp(i32::MIN as i128, i32::MAX as i128) as i32),
		)?;
		gen_pass_result.serialize_field("RandNext", &random)?;
		gen_pass_result.serialize_field("Hash", &hash)?;
		gen_pass_result.serialize_field("Skipped", &is_skipped)?;
		gen_pass_result.end()
	}
}

impl<'de> Deserialize<'de> for GeneratorPassResult {
	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
	where
		D: Deserializer<'de>,
	{
		#[derive(Deserialize)]
		#[serde(field_identifier, rename_all = "PascalCase")]
		enum Field {
			DurationMs,
			RandNext,
			Hash,
			Skipped,
		}

		struct GeneratorPassResultVisitor;

		impl<'de> Visitor<'de> for GeneratorPassResultVisitor {
			type Value = GeneratorPassResult;

			fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
				formatter.write_str("struct GeneratorPassResult")
			}

			fn visit_seq<V>(self, mut seq: V) -> Result<GeneratorPassResult, V::Error>
			where
				V: SeqAccess<'de>,
			{
				let duration_ms = seq
					.next_element()?
					.ok_or_else(|| de::Error::invalid_length(0, &self))?;
				let random = seq
					.next_element()?
					.ok_or_else(|| de::Error::invalid_length(1, &self))?;
				let hash = seq
					.next_element()?
					.ok_or_else(|| de::Error::invalid_length(2, &self))?;
				let is_skipped = seq
					.next_element()?
					.ok_or_else(|| de::Error::invalid_length(3, &self))?;

				if is_skipped {
					return Ok(GeneratorPassResult::Skipped);
				}

				Ok(GeneratorPassResult::Result {
					duration: Duration::milliseconds(duration_ms),
					random,
					hash,
				})
			}

			fn visit_map<V>(self, mut map: V) -> Result<GeneratorPassResult, V::Error>
			where
				V: MapAccess<'de>,
			{
				let mut duration_ms: Option<i64> = None;
				let mut random: Option<i32> = None;
				let mut hash: Option<Option<u32>> = None;
				let mut is_skipped: Option<bool> = None;
				while let Some(key) = map.next_key()? {
					match key {
						Field::DurationMs => {
							if duration_ms.is_some() {
								return Err(de::Error::duplicate_field("DurationMs"));
							}
							duration_ms = Some(map.next_value()?);
						}

						Field::RandNext => {
							if random.is_some() {
								return Err(de::Error::duplicate_field("RandNext"));
							}
							random = Some(map.next_value()?);
						}

						Field::Hash => {
							if hash.is_some() {
								return Err(de::Error::duplicate_field("Hash"));
							}
							hash = Some(map.next_value()?);
						}

						Field::Skipped => {
							if is_skipped.is_some() {
								return Err(de::Error::duplicate_field("Skipped"));
							}
							is_skipped = Some(map.next_value()?)
						}
					}
				}

				if is_skipped.ok_or_else(|| de::Error::missing_field("Skipped"))? {
					return Ok(GeneratorPassResult::Skipped);
				}

				let duration_ms =
					duration_ms.ok_or_else(|| de::Error::missing_field("DurationMs"))?;
				let random = random.ok_or_else(|| de::Error::missing_field("RandNext"))?;
				let hash = hash.ok_or_else(|| de::Error::missing_field("Hash"))?;
				Ok(GeneratorPassResult::Result {
					duration: Duration::milliseconds(duration_ms),
					random,
					hash,
				})
			}
		}

		const FIELDS: &[&str] = &["DurationMs", "RandNext", "Hash", "Skipped"];
		deserializer.deserialize_struct("GeneratorPassResult", FIELDS, GeneratorPassResultVisitor)
	}
}