diff --git a/src/snapshot_middleware/dir.rs b/src/snapshot_middleware/dir.rs index b3c1a126..1a44d88a 100644 --- a/src/snapshot_middleware/dir.rs +++ b/src/snapshot_middleware/dir.rs @@ -4,12 +4,13 @@ use rbx_dom_weak::{RbxId, RbxTree}; use crate::{ snapshot::{InstanceMetadata, InstanceSnapshot}, - vfs::{DirectorySnapshot, Vfs, VfsEntry, VfsFetcher, VfsSnapshot}, + vfs::{DirectorySnapshot, FsResultExt, Vfs, VfsEntry, VfsFetcher, VfsSnapshot}, }; use super::{ context::InstanceSnapshotContext, error::SnapshotError, + meta_file::DirectoryMetadata, middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware}, snapshot_from_instance, snapshot_from_vfs, }; @@ -44,7 +45,9 @@ impl SnapshotMiddleware for SnapshotDir { .ok_or_else(|| SnapshotError::file_name_bad_unicode(entry.path()))? .to_string(); - Ok(Some(InstanceSnapshot { + let meta_path = entry.path().join("init.meta.json"); + + let mut snapshot = InstanceSnapshot { snapshot_id: None, metadata: InstanceMetadata { instigating_source: Some(entry.path().to_path_buf().into()), @@ -55,7 +58,15 @@ impl SnapshotMiddleware for SnapshotDir { class_name: Cow::Borrowed("Folder"), properties: HashMap::new(), children: snapshot_children, - })) + }; + + if let Some(meta_entry) = vfs.get(meta_path).with_not_found()? { + let meta_contents = meta_entry.contents(vfs)?; + let mut metadata = DirectoryMetadata::from_slice(&meta_contents); + metadata.apply_all(&mut snapshot); + } + + Ok(Some(snapshot)) } fn from_instance(tree: &RbxTree, id: RbxId) -> SnapshotFileResult { diff --git a/src/snapshot_middleware/lua.rs b/src/snapshot_middleware/lua.rs index 8f3266d5..bd6c54b2 100644 --- a/src/snapshot_middleware/lua.rs +++ b/src/snapshot_middleware/lua.rs @@ -126,10 +126,17 @@ fn snapshot_init( if let Some(init_entry) = vfs.get(init_path).with_not_found()? { if let Some(dir_snapshot) = SnapshotDir::from_vfs(context, vfs, folder_entry)? { if let Some(mut init_snapshot) = snapshot_lua_file(vfs, &init_entry)? { + if dir_snapshot.class_name != "Folder" { + panic!( + "init.lua, init.server.lua, and init.client.lua can \ + only be used if the instance produced by the parent \ + directory would be a Folder." + ); + } + init_snapshot.name = dir_snapshot.name; init_snapshot.children = dir_snapshot.children; - // TODO: Metadata - // TODO: Validate directory class name is "Folder" + // TODO: Apply metadata from folder return Ok(Some(init_snapshot)); } diff --git a/src/snapshot_middleware/meta_file.rs b/src/snapshot_middleware/meta_file.rs index 9103dfc0..db8e1b40 100644 --- a/src/snapshot_middleware/meta_file.rs +++ b/src/snapshot_middleware/meta_file.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{borrow::Cow, collections::HashMap}; use rbx_dom_weak::UnresolvedRbxValue; use rbx_reflection::try_resolve_value; @@ -55,3 +55,65 @@ impl AdjacentMetadata { // TODO: Add method to allow selectively applying parts of metadata and // throwing errors if invalid parts are specified. } + +/// Represents metadata that affects the instance resulting from the containing +/// folder. +/// +/// This is always sourced from a file named init.meta.json. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DirectoryMetadata { + #[serde(skip_serializing_if = "Option::is_none")] + pub ignore_unknown_instances: Option, + + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub properties: HashMap, + + #[serde(skip_serializing_if = "Option::is_none")] + pub class_name: Option, +} + +impl DirectoryMetadata { + pub fn from_slice(slice: &[u8]) -> Self { + serde_json::from_slice(slice) + // TODO: Turn into error type + .expect("init.meta.json file was malformed") + } + + pub fn apply_all(&mut self, snapshot: &mut InstanceSnapshot) { + self.apply_ignore_unknown_instances(snapshot); + self.apply_class_name(snapshot); + self.apply_properties(snapshot); + } + + fn apply_class_name(&mut self, snapshot: &mut InstanceSnapshot) { + if let Some(class_name) = self.class_name.take() { + if snapshot.class_name != "Folder" { + // TODO: Turn into error type + panic!("className in init.meta.json can only be specified if the affected directory would turn into a Folder instance."); + } + + snapshot.class_name = Cow::Owned(class_name); + } + } + + fn apply_ignore_unknown_instances(&mut self, snapshot: &mut InstanceSnapshot) { + if let Some(ignore) = self.ignore_unknown_instances.take() { + snapshot.metadata.ignore_unknown_instances = ignore; + } + } + + fn apply_properties(&mut self, snapshot: &mut InstanceSnapshot) { + let class_name = &snapshot.class_name; + + let source_properties = self.properties.drain().map(|(key, value)| { + try_resolve_value(class_name, &key, &value) + .map(|resolved| (key, resolved)) + .expect("TODO: Handle rbx_reflection errors") + }); + + for (key, value) in source_properties { + snapshot.properties.insert(key, value); + } + } +}