diff --git a/sdml-parse/Cargo.toml b/sdml-parse/Cargo.toml index 399c689..dbd4178 100644 --- a/sdml-parse/Cargo.toml +++ b/sdml-parse/Cargo.toml @@ -21,6 +21,7 @@ sdml-errors = { version = "0.3.1", path = "../sdml-errors" } search_path = "0.1.4" serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" +serial_test = "3.2.0" tracing = "0.1.40" tree-sitter = "0.23" tree-sitter-sdml = "0.3.3" diff --git a/sdml-parse/src/load.rs b/sdml-parse/src/load.rs index dca4e48..98ce29c 100644 --- a/sdml-parse/src/load.rs +++ b/sdml-parse/src/load.rs @@ -19,6 +19,7 @@ use sdml_errors::{Error, FileId}; use search_path::SearchPath; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::env; use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; @@ -51,6 +52,9 @@ pub const SDML_FILE_EXTENSION_LONG: &str = "sdml"; /// The name used for resolver catalog files. pub const SDML_CATALOG_FILE_NAME: &str = "sdml-catalog.json"; +/// The environment variable used to override resolver catalog file location. +pub const SDML_CATALOG_FILE_VARIABLE: &str = "SDML_CATALOG"; + /// /// The loader is used to manage the process of creating an in-memory model from file-system resources. /// @@ -149,8 +153,20 @@ impl Default for FsModuleResolver { // 2. Add the current directory to the search path search_path.prepend_cwd(); - // 3. Load any catalog file found in the search path - let catalog = ModuleCatalog::load_from_current(true); + // 3. Load catalog file + let catalog = match env::var(SDML_CATALOG_FILE_VARIABLE) { + // If the environment variable is provided, load it from the location provided + Ok(catalog_file) => { + let catalog_file_path = PathBuf::from(catalog_file); + let module_catalog = ModuleCatalog::load_from_file(catalog_file_path.as_path()); + if module_catalog.is_none() { + error!("The path to module catalog was provided through environment variable, yet it failed to load."); + } + module_catalog + }, + // If the environment variable is not provided, load it from the current directory (or any parent directory) + _ => ModuleCatalog::load_from_current(true), + }; let _self = Self { catalog, @@ -435,6 +451,7 @@ impl ModuleCatalog { /// exist. /// fn load_from_file(file: &Path) -> Option { + trace!("ModuleCatalog::load_from_file({file:?})"); match std::fs::read_to_string(file) { Ok(source) => match serde_json::from_str::(&source) { Ok(mut catalog) => { diff --git a/sdml-parse/src/parse/values.rs b/sdml-parse/src/parse/values.rs index 53114a0..2e1e446 100644 --- a/sdml-parse/src/parse/values.rs +++ b/sdml-parse/src/parse/values.rs @@ -273,7 +273,7 @@ fn parse_binary<'a>( { context.check_if_error(&node, RULE_NAME)?; let value = context.node_source(&node)?; - let value = u8::from_str(value).expect("Invalid value for Byte"); + let value = u8::from_str_radix(value, 16).expect("Invalid value for Byte"); result.push(value); } diff --git a/sdml-parse/tests/catalog_examples/custom-catalog.json b/sdml-parse/tests/catalog_examples/custom-catalog.json new file mode 100644 index 0000000..18a1306 --- /dev/null +++ b/sdml-parse/tests/catalog_examples/custom-catalog.json @@ -0,0 +1,11 @@ +{ + "base": "https://examples.sdml.io/", + "entries": { + "campaign": { + "item": { + "relative_url": "campaign#", + "relative_path": "../examples/entity_empty.sdm" + } + } + } +} diff --git a/sdml-parse/tests/examples/ron/annotation_single_binary.ron b/sdml-parse/tests/examples/ron/annotation_single_binary.ron index bd7a3b1..8c88a16 100644 --- a/sdml-parse/tests/examples/ron/annotation_single_binary.ron +++ b/sdml-parse/tests/examples/ron/annotation_single_binary.ron @@ -73,7 +73,36 @@ Module { value: Simple( Binary( Binary( - [], + [ + 82, + 50, + 57, + 118, + 90, + 67, + 66, + 67, + 101, + 87, + 85, + 103, + 81, + 51, + 74, + 49, + 90, + 87, + 119, + 103, + 86, + 50, + 57, + 121, + 98, + 71, + 81, + 75, + ], ), ), ), diff --git a/sdml-parse/tests/test_examples.rs b/sdml-parse/tests/test_examples.rs index fd7d5c7..4fdb251 100644 --- a/sdml-parse/tests/test_examples.rs +++ b/sdml-parse/tests/test_examples.rs @@ -42,7 +42,7 @@ macro_rules! test_example { )); let expected = ::std::path::PathBuf::from( format!( - "{}/{}/{}.ron", + "{}/{}/ron/{}.ron", MANIFEST_PATH, TEST_PATH, test_name diff --git a/sdml-parse/tests/test_load_catalog.rs b/sdml-parse/tests/test_load_catalog.rs new file mode 100644 index 0000000..25495f7 --- /dev/null +++ b/sdml-parse/tests/test_load_catalog.rs @@ -0,0 +1,91 @@ +use sdml_core::{load::ModuleLoader, model::identifiers::Identifier, store::ModuleStore}; +use sdml_parse::load::SDML_CATALOG_FILE_VARIABLE; +use serial_test::serial; +use std::str::FromStr; +use url::Url; + +const MANIFEST_PATH: &str = env!("CARGO_MANIFEST_DIR"); +const TEST_PATH: &str = "tests/catalog_examples"; + +const CATALOG_FILE: &str = "custom-catalog.json"; +const MODULE_NAME: &str = "campaign"; + +fn set_env_variable(env_key: &str, env_value: Option) { + match env_value { + Some(v) => std::env::set_var(env_key, v), + None => std::env::remove_var(env_key), + } +} + +fn with_env_variable(env_key: &str, env_value: Option<&str>, test: F) +where + F : FnOnce() + std::panic::UnwindSafe, +{ + // Set the environment variable + let old_value = std::env::var(env_key).ok(); + + set_env_variable(env_key, env_value.map(|x| { String::from(x)})); + + // Run the test, catching any panic + let result = std::panic::catch_unwind(|| { + test(); + }); + + // Clean-up / restore environment variable + set_env_variable(env_key, old_value); + + // Propagate the panic if it occurred + if let Err(err) = result { + std::panic::resume_unwind(err); + } +} + +#[test] +#[serial] +fn test_load_without_catalogue() { + with_env_variable(SDML_CATALOG_FILE_VARIABLE, None, || { + let mut cache = ::sdml_core::store::InMemoryModuleCache::default().with_stdlib(); + let mut loader = ::sdml_parse::load::FsModuleLoader::default(); + let module_name = Identifier::from_str(MODULE_NAME).unwrap(); + + loader.load( + &module_name, + loader.get_file_id(&module_name), + &mut cache, + true, + ).expect_err("Error: Should have failed to load the module."); + }); +} + +#[test] +#[serial] +fn test_load_with_catalogue() { + let catalog_path = ::std::path::PathBuf::from( + format!( + "{}/{}/{}", + MANIFEST_PATH, + TEST_PATH, + CATALOG_FILE, + )); + + with_env_variable(SDML_CATALOG_FILE_VARIABLE, catalog_path.to_str(), || { + let mut cache = ::sdml_core::store::InMemoryModuleCache::default().with_stdlib(); + let mut loader = ::sdml_parse::load::FsModuleLoader::default(); + let module_name = Identifier::from_str(MODULE_NAME).unwrap(); + + loader.load( + &module_name, + loader.get_file_id(&module_name), + &mut cache, + true, + ).expect("Error: Should have been able to load the module."); + + let module = cache + .get(&module_name) + .expect("Error: Module not found in cache."); + + let url = Url::from_str("https://examples.sdml.io/campaign#").ok(); + + assert_eq!(module.base_uri().map(|x| { x.value().clone() }), url); + }); +}