diff options
| -rw-r--r-- | Cargo.lock | 288 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/shirabe-external-packages/src/json_schema/mod.rs | 3 | ||||
| -rw-r--r-- | crates/shirabe-external-packages/src/json_schema/validator.rs | 30 | ||||
| -rw-r--r-- | crates/shirabe-external-packages/src/lib.rs | 1 | ||||
| -rw-r--r-- | crates/shirabe-php-shim/src/fs.rs | 6 | ||||
| -rw-r--r-- | crates/shirabe/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/shirabe/src/json/json_file.rs | 52 | ||||
| -rw-r--r-- | crates/shirabe/tests/json/composer_schema_test.rs | 135 | ||||
| -rw-r--r-- | crates/shirabe/tests/json/json_file_test.rs | 99 |
10 files changed, 500 insertions, 116 deletions
@@ -20,6 +20,20 @@ dependencies = [ ] [[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -29,6 +43,12 @@ dependencies = [ ] [[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -95,6 +115,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -120,12 +155,24 @@ dependencies = [ ] [[package]] +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + +[[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -313,6 +360,12 @@ dependencies = [ ] [[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] name = "deflate64" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -368,6 +421,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + +[[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -393,6 +455,17 @@ dependencies = [ ] [[package]] +name = "fancy-regex" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + +[[package]] name = "fastrand" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -415,6 +488,17 @@ dependencies = [ ] [[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + +[[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -427,6 +511,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -436,6 +526,16 @@ dependencies = [ ] [[package]] +name = "fraction" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" +dependencies = [ + "lazy_static", + "num", +] + +[[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -568,7 +668,18 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", ] [[package]] @@ -939,6 +1050,39 @@ dependencies = [ ] [[package]] +name = "jsonschema" +version = "0.46.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8374249b1bdce1c1773a09fa294347e19e4bfeb86d845c2d8ebaf8a8dbf11233" +dependencies = [ + "ahash", + "bytecount", + "data-encoding", + "email_address", + "fancy-regex", + "fraction", + "getrandom 0.3.4", + "idna", + "itoa", + "num-cmp", + "num-traits", + "percent-encoding", + "referencing", + "regex", + "regex-syntax", + "serde", + "serde_json", + "unicode-general-category", + "uuid-simd", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1011,6 +1155,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + +[[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1038,12 +1188,82 @@ dependencies = [ ] [[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] name = "num-conv" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1065,6 +1285,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1280,6 +1506,43 @@ dependencies = [ ] [[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "referencing" +version = "0.46.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65a910f9d63351f9c9d7dc3053d02b214ea3a005917140c70087d7b59cbc5bb1" +dependencies = [ + "ahash", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown 0.16.1", + "itoa", + "micromap", + "parking_lot", + "percent-encoding", + "serde_json", +] + +[[package]] name = "regex" version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1651,6 +1914,7 @@ dependencies = [ "base64", "chrono", "indexmap", + "jsonschema", "md5", "regex", "serde", @@ -2079,6 +2343,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + +[[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2115,12 +2385,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] +name = "uuid-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -20,6 +20,7 @@ base64 = "0.22.1" chrono = { version = "0.4.44", features = ["serde"] } fastrand = "2.4.1" indexmap = { version = "2.14.0", features = ["serde"] } +jsonschema = { version = "0.46.6", default-features = false } md5 = "0.7.0" regex = "1.12.3" reqwest = "0.13.4" diff --git a/crates/shirabe-external-packages/src/json_schema/mod.rs b/crates/shirabe-external-packages/src/json_schema/mod.rs deleted file mode 100644 index 49febb8..0000000 --- a/crates/shirabe-external-packages/src/json_schema/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod validator; - -pub use validator::*; diff --git a/crates/shirabe-external-packages/src/json_schema/validator.rs b/crates/shirabe-external-packages/src/json_schema/validator.rs deleted file mode 100644 index ede3762..0000000 --- a/crates/shirabe-external-packages/src/json_schema/validator.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! ref: composer/vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php - -use shirabe_php_shim::PhpMixed; - -#[derive(Debug)] -pub struct Validator; - -impl Default for Validator { - fn default() -> Self { - Self::new() - } -} - -impl Validator { - pub fn new() -> Self { - todo!() - } - - pub fn check(&mut self, _data: &PhpMixed, _schema: &PhpMixed) -> anyhow::Result<()> { - todo!() - } - - pub fn is_valid(&self) -> bool { - todo!() - } - - pub fn get_errors(&self) -> Vec<String> { - todo!() - } -} diff --git a/crates/shirabe-external-packages/src/lib.rs b/crates/shirabe-external-packages/src/lib.rs index eef1e37..0c37152 100644 --- a/crates/shirabe-external-packages/src/lib.rs +++ b/crates/shirabe-external-packages/src/lib.rs @@ -1,5 +1,4 @@ pub mod composer; -pub mod json_schema; pub mod psr; pub mod seld; pub mod symfony; diff --git a/crates/shirabe-php-shim/src/fs.rs b/crates/shirabe-php-shim/src/fs.rs index e70d397..ee2bbee 100644 --- a/crates/shirabe-php-shim/src/fs.rs +++ b/crates/shirabe-php-shim/src/fs.rs @@ -798,6 +798,12 @@ pub fn file_put_contents3(_filename: &str, _data: &str, _flags: i64) -> Option<i } pub fn file_get_contents(path: impl AsRef<std::path::Path>) -> Option<String> { + let path = path.as_ref(); + // PHP supports the file:// stream wrapper; strip it to read the local file. + let path = path + .to_str() + .and_then(|s| s.strip_prefix("file://")) + .map_or(path, std::path::Path::new); std::fs::read(path) .ok() .map(|bytes| String::from_utf8_lossy(&bytes).into_owned()) diff --git a/crates/shirabe/Cargo.toml b/crates/shirabe/Cargo.toml index eb34838..1b07df4 100644 --- a/crates/shirabe/Cargo.toml +++ b/crates/shirabe/Cargo.toml @@ -15,6 +15,7 @@ async-trait.workspace = true base64.workspace = true chrono.workspace = true indexmap.workspace = true +jsonschema.workspace = true md5.workspace = true regex.workspace = true serde.workspace = true diff --git a/crates/shirabe/src/json/json_file.rs b/crates/shirabe/src/json/json_file.rs index e6cd990..5da1a27 100644 --- a/crates/shirabe/src/json/json_file.rs +++ b/crates/shirabe/src/json/json_file.rs @@ -5,7 +5,6 @@ use crate::util::Silencer; use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; -use shirabe_external_packages::json_schema::Validator; use shirabe_external_packages::seld::json_lint::JsonParser; use shirabe_external_packages::seld::json_lint::ParsingException; use shirabe_php_shim::{ @@ -371,7 +370,7 @@ impl JsonFile { if schema == Self::STRICT_SCHEMA && is_composer_schema_file { schema_data = json_decode(&file_get_contents(&schema_file).unwrap_or_default(), false)?; - if let PhpMixed::Array(map) = &mut schema_data { + if let PhpMixed::Object(map) = &mut schema_data { map.insert("additionalProperties".to_string(), PhpMixed::Bool(false)); map.insert( "required".to_string(), @@ -394,16 +393,31 @@ impl JsonFile { schema_data = PhpMixed::Array(m); } - let mut validator = Validator::new(); // convert assoc arrays to objects - let data_converted = json_decode(&json_encode_ex(data, 0).unwrap_or_default(), false)?; - validator.check(&data_converted, &schema_data)?; + let schema_value = serde_json::to_value(&schema_data)?; + let data_value = serde_json::to_value(data)?; + let validator = jsonschema::options() + .with_retriever(FileRetriever) + .build(&schema_value) + .map_err(|e| anyhow::anyhow!("{e}"))?; - if !validator.is_valid() { - // TODO(phase-c): Validator::get_errors currently returns Vec<String>; original PHP - // exposes [{property, message}, ...]. Until the validator shim is enriched, surface raw - // error strings without prop/message splitting. - let errors: Vec<String> = validator.get_errors(); + let errors: Vec<String> = validator + .iter_errors(&data_value) + .map(|error| { + let property = error + .instance_path() + .as_str() + .trim_start_matches('/') + .replace('/', "."); + if property.is_empty() { + error.to_string() + } else { + format!("{} : {}", property, error) + } + }) + .collect(); + + if !errors.is_empty() { return Err(JsonValidationException::new( format!("\"{}\" does not match the expected JSON schema", source), errors, @@ -550,3 +564,21 @@ impl JsonFile { Self::INDENT_DEFAULT.to_string() } } + +#[derive(Debug)] +struct FileRetriever; + +impl jsonschema::Retrieve for FileRetriever { + fn retrieve( + &self, + uri: &jsonschema::Uri<String>, + ) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> { + match uri.scheme().as_str() { + "file" => { + let file = std::fs::File::open(uri.path().as_str())?; + Ok(serde_json::from_reader(file)?) + } + scheme => Err(format!("Unknown scheme {scheme}").into()), + } + } +} diff --git a/crates/shirabe/tests/json/composer_schema_test.rs b/crates/shirabe/tests/json/composer_schema_test.rs index 08415f6..9cbea15 100644 --- a/crates/shirabe/tests/json/composer_schema_test.rs +++ b/crates/shirabe/tests/json/composer_schema_test.rs @@ -1,33 +1,144 @@ //! ref: composer/tests/Composer/Test/Json/ComposerSchemaTest.php +//! +//! WORDING NOTE: the upstream PHP tests assert justinrainbow's structured +//! `{property, message, constraint}` error arrays (e.g. "Does not match the regex pattern …", +//! "Array value found, but a string is required", "Does not have a value in the enumeration …"). +//! Shirabe validates with the `jsonschema` crate, whose message wording differs. These ports +//! therefore assert the equivalent `property : message` strings using jsonschema's wording. +//! Only the wording changed — the validation behavior (which inputs are valid/invalid and on +//! which property) is identical to upstream. + +use shirabe::json::{JsonFile, JsonValidationException}; +use shirabe_php_shim::json_decode; + +const NAME_PATTERN: &str = r#"^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$"#; +const VERSION_PATTERN: &str = r#"^[vV]?\d+(?:[.-]\d+){0,3}[._-]?(?:(?:[sS][tT][aA][bB][lL][eE]|[bB][eE][tT][aA]|[bB]|[rR][cC]|[aA][lL][pP][hH][aA]|[aA]|[pP][aA][tT][cC][hH]|[pP][lL]|[pP])(?:(?:[.-]?\d+)*)?)?(?:[.-]?[dD][eE][vV]|\.x-dev)?(?:\+.*)?$|^dev-.*$"#; + +/// Ports `ComposerSchemaTest::check`: validate against the bundled `composer-schema.json` +/// (the `{"$ref": "file://…"}` wrapper used by `LAX_SCHEMA`), returning the validation error +/// strings, or an empty vec when the document is valid. +fn check(json: &str) -> Vec<String> { + let data = json_decode(json, false).unwrap(); + match JsonFile::validate_json_schema("test", &data, JsonFile::LAX_SCHEMA, None) { + Ok(_) => Vec::new(), + Err(e) => e + .downcast_ref::<JsonValidationException>() + .unwrap() + .get_errors() + .clone(), + } +} -// These validate documents against the bundled composer-schema.json via JsonFile's -// json-schema validator, which is not ported. -#[ignore = "JsonSchema Validator::get_errors returns Vec<String>; cannot represent structured property/message/constraint error arrays asserted here, and Validator::validate is not ported"] #[test] fn test_name_pattern() { - todo!() + let expected_error = vec![format!( + r#"name : "vendor/-pack__age" does not match "{NAME_PATTERN}""# + )]; + let json = r#"{"name": "vendor/-pack__age", "description": "description"}"#; + assert_eq!(expected_error, check(json)); + + let expected_error = vec![format!( + r#"name : "Vendor/Package" does not match "{NAME_PATTERN}""# + )]; + let json = r#"{"name": "Vendor/Package", "description": "description"}"#; + assert_eq!(expected_error, check(json)); } -#[ignore = "JsonSchema Validator::get_errors returns Vec<String>; cannot represent structured property/message/constraint error arrays asserted here, and Validator::validate is not ported"] #[test] fn test_version_pattern() { - todo!() + let versions: &[(&str, bool)] = &[ + ("1.0.0", true), + ("1.0.2", true), + ("1.1.0", true), + ("1.0.0-dev", true), + ("1.0.0-Alpha", true), + ("1.0.0-ALPHA", true), + ("1.0.0-alphA", true), + ("1.0.0-alpha3", true), + ("1.0.0-Alpha3", true), + ("1.0.0-ALPHA3", true), + ("1.0.0-Beta", true), + ("1.0.0-BETA", true), + ("1.0.0-betA", true), + ("1.0.0-beta232", true), + ("1.0.0-Beta232", true), + ("1.0.0-BETA232", true), + ("10.4.13beta.2", true), + ("1.0.0.RC.15-dev", true), + ("1.0.0-RC", true), + ("v2.0.4-p", true), + ("dev-master", true), + ("0.2.5.4", true), + ("12345678-123456", true), + ("20100102-203040-p1", true), + ("2010-01-02.5", true), + ("0.2.5.4-rc.2", true), + ("dev-feature+issue-1", true), + ("1.0.0-alpha.3.1+foo/-bar", true), + ("00.01.03.04", true), + ("041.x-dev", true), + ("dev-foo bar", true), + ("invalid", false), + ("1.0be", false), + ("1.0.0-meh", false), + ("feature-foo", false), + ("1.0 .2", false), + ]; + + for &(version, is_valid) in versions { + let json = format!( + r#"{{"name": "vendor/package", "description": "description", "version": "{version}"}}"# + ); + if is_valid { + assert!(check(&json).is_empty(), "expected {version} to be valid"); + } else { + let expected_error = vec![format!( + r#"version : "{version}" does not match "{VERSION_PATTERN}""# + )]; + assert_eq!( + expected_error, + check(&json), + "expected {version} to be invalid" + ); + } + } } -#[ignore = "JsonSchema Validator::get_errors returns Vec<String>; cannot represent structured property/message/constraint error arrays asserted here, and Validator::validate is not ported"] #[test] fn test_optional_abandoned_property() { - todo!() + let json = r#"{"name": "vendor/package", "description": "description", "abandoned": true}"#; + assert!(check(json).is_empty()); } -#[ignore = "JsonSchema Validator::get_errors returns Vec<String>; cannot represent structured property/message/constraint error arrays asserted here, and Validator::validate is not ported"] #[test] fn test_require_types() { - todo!() + let json = + r#"{"name": "vendor/package", "description": "description", "require": {"a": ["b"]} }"#; + let expected_error = vec![r#"require.a : ["b"] is not of type "string""#.to_string()]; + assert_eq!(expected_error, check(json)); } -#[ignore = "JsonSchema Validator::get_errors returns Vec<String>; cannot represent structured property/message/constraint error arrays asserted here, and Validator::validate is not ported"] #[test] fn test_minimum_stability_values() { - todo!() + let enum_error = |value: &str| { + vec![format!( + r#"minimum-stability : "{value}" is not one of "dev", "alpha" or 4 other candidates"# + )] + }; + + let json = r#"{ "name": "vendor/package", "description": "generic description", "minimum-stability": "" }"#; + assert_eq!(enum_error(""), check(json), "empty string"); + + let json = r#"{ "name": "vendor/package", "description": "generic description", "minimum-stability": "dummy" }"#; + assert_eq!(enum_error("dummy"), check(json), "dummy"); + + let json = r#"{ "name": "vendor/package", "description": "generic description", "minimum-stability": "devz" }"#; + assert_eq!(enum_error("devz"), check(json), "devz"); + + for stability in ["dev", "alpha", "beta", "rc", "RC", "stable"] { + let json = format!( + r#"{{ "name": "vendor/package", "description": "generic description", "minimum-stability": "{stability}" }}"# + ); + assert!(check(&json).is_empty(), "expected {stability} to be valid"); + } } diff --git a/crates/shirabe/tests/json/json_file_test.rs b/crates/shirabe/tests/json/json_file_test.rs index 032d0fd..d3e1048 100644 --- a/crates/shirabe/tests/json/json_file_test.rs +++ b/crates/shirabe/tests/json/json_file_test.rs @@ -37,20 +37,9 @@ fn fixture_path(name: &str) -> std::path::PathBuf { .join(name) } -/// ref: TestCase::createTempFile (PHP tempnam()) -fn create_temp_file() -> String { - let mut path = std::env::temp_dir(); - let unique = format!( - "shirabe_jsonfiletest_{}_{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - ); - path.push(unique); - std::fs::write(&path, b"").unwrap(); - path.to_str().unwrap().to_string() +/// ref: TestCase::createTempFile. +fn create_temp_file() -> tempfile::NamedTempFile { + tempfile::NamedTempFile::new().expect("failed to create a temporary file") } #[test] @@ -124,7 +113,6 @@ fn test_parse_error_detect_missing_colon() { } #[test] -#[ignore] fn test_simple_json_string() { let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); data.insert( @@ -136,7 +124,6 @@ fn test_simple_json_string() { } #[test] -#[ignore] fn test_trailing_backslash() { let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); data.insert( @@ -148,7 +135,6 @@ fn test_trailing_backslash() { } #[test] -#[ignore] fn test_format_empty_array() { let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); data.insert("test".to_string(), PhpMixed::List(vec![])); @@ -158,7 +144,6 @@ fn test_format_empty_array() { } #[test] -#[ignore] fn test_escape() { let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); data.insert( @@ -170,7 +155,6 @@ fn test_escape() { } #[test] -#[ignore] fn test_unicode() { let mut data: IndexMap<String, PhpMixed> = IndexMap::new(); data.insert( @@ -245,7 +229,6 @@ fn test_preserve_indentation_after_read() { } #[test] -#[ignore] fn test_overwrites_indentation_by_default() { let src = fixture_path("tabs.json"); let dst = fixture_path("tabs2.json"); @@ -265,7 +248,6 @@ fn test_overwrites_indentation_by_default() { } #[test] -#[ignore] fn test_schema_validation() { let path = fixture_path("composer.json"); let json = JsonFile::new(path.to_str().unwrap().to_string(), None, None).unwrap(); @@ -274,13 +256,15 @@ fn test_schema_validation() { } #[test] -#[ignore] fn test_schema_validation_error() { - let file = create_temp_file(); + // WORDING NOTE: upstream asserts justinrainbow's "name : NULL value found, but a string is + // required". The jsonschema crate reports the same violation with different wording. + let file_tmp = create_temp_file(); + let file = file_tmp.path().to_str().unwrap().to_string(); std::fs::write(&file, b"{ \"name\": null }").unwrap(); let json = JsonFile::new(file.clone(), None, None).unwrap(); let expected_message = format!("\"{}\" does not match the expected JSON schema", file); - let expected_error = "name : NULL value found, but a string is required".to_string(); + let expected_error = "name : null is not of type \"string\"".to_string(); let err = json .validate_schema(JsonFile::STRICT_SCHEMA, None) @@ -295,14 +279,15 @@ fn test_schema_validation_error() { let e = err.downcast_ref::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); assert!(e.get_errors().contains(&expected_error)); - - std::fs::remove_file(&file).unwrap(); } #[test] -#[ignore] fn test_schema_validation_lax_additional_properties() { - let file = create_temp_file(); + // WORDING NOTE: upstream asserts justinrainbow's "The property foo is not defined and the + // definition does not allow additional properties". The jsonschema crate reports the same + // additionalProperties violation with different wording. + let file_tmp = create_temp_file(); + let file = file_tmp.path().to_str().unwrap().to_string(); std::fs::write( &file, b"{ \"name\": \"vendor/package\", \"description\": \"generic description\", \"foo\": \"bar\" }", @@ -319,21 +304,21 @@ fn test_schema_validation_lax_additional_properties() { e.get_message() ); assert_eq!( - &vec![ - "The property foo is not defined and the definition does not allow additional properties" - .to_string() - ], + &vec!["Additional properties are not allowed ('foo' was unexpected)".to_string()], e.get_errors() ); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); - std::fs::remove_file(&file).unwrap(); } #[test] -#[ignore] fn test_schema_validation_lax_required() { - let file = create_temp_file(); + // WORDING NOTE: upstream asserts justinrainbow's property-prefixed strings such as + // "name : The property name is required". The jsonschema crate reports the same required + // violations as "\"name\" is a required property" (no property prefix; the property name is + // embedded in the message). + let file_tmp = create_temp_file(); + let file = file_tmp.path().to_str().unwrap().to_string(); let json = JsonFile::new(file.clone(), None, None).unwrap(); let expected_message = format!("\"{}\" does not match the expected JSON schema", file); @@ -345,8 +330,8 @@ fn test_schema_validation_lax_required() { let e = err.downcast_ref::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); let errors = e.get_errors(); - assert!(errors.contains(&"name : The property name is required".to_string())); - assert!(errors.contains(&"description : The property description is required".to_string())); + assert!(errors.contains(&"\"name\" is a required property".to_string())); + assert!(errors.contains(&"\"description\" is a required property".to_string())); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); std::fs::write(&file, b"{ \"name\": \"vendor/package\" }").unwrap(); @@ -356,7 +341,7 @@ fn test_schema_validation_lax_required() { let e = err.downcast_ref::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); assert_eq!( - &vec!["description : The property description is required".to_string()], + &vec!["\"description\" is a required property".to_string()], e.get_errors() ); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); @@ -368,7 +353,7 @@ fn test_schema_validation_lax_required() { let e = err.downcast_ref::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); assert_eq!( - &vec!["name : The property name is required".to_string()], + &vec!["\"name\" is a required property".to_string()], e.get_errors() ); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); @@ -380,8 +365,8 @@ fn test_schema_validation_lax_required() { let e = err.downcast_ref::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); let errors = e.get_errors(); - assert!(errors.contains(&"name : The property name is required".to_string())); - assert!(errors.contains(&"description : The property description is required".to_string())); + assert!(errors.contains(&"\"name\" is a required property".to_string())); + assert!(errors.contains(&"\"description\" is a required property".to_string())); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); std::fs::write(&file, b"{ \"type\": \"project\" }").unwrap(); @@ -391,8 +376,8 @@ fn test_schema_validation_lax_required() { let e = err.downcast_ref::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); let errors = e.get_errors(); - assert!(errors.contains(&"name : The property name is required".to_string())); - assert!(errors.contains(&"description : The property description is required".to_string())); + assert!(errors.contains(&"\"name\" is a required property".to_string())); + assert!(errors.contains(&"\"description\" is a required property".to_string())); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); std::fs::write( @@ -402,21 +387,20 @@ fn test_schema_validation_lax_required() { .unwrap(); json.validate_schema(JsonFile::STRICT_SCHEMA, None).unwrap(); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); - - std::fs::remove_file(&file).unwrap(); } #[test] -#[ignore] fn test_custom_schema_validation_lax() { - let file = create_temp_file(); + let file_tmp = create_temp_file(); + let file = file_tmp.path().to_str().unwrap().to_string(); std::fs::write( &file, b"{ \"custom\": \"property\", \"another custom\": \"property\" }", ) .unwrap(); - let schema = create_temp_file(); + let schema_tmp = create_temp_file(); + let schema = schema_tmp.path().to_str().unwrap().to_string(); std::fs::write( &schema, b"{ \"properties\": { \"custom\": { \"type\": \"string\" }}}", @@ -427,18 +411,16 @@ fn test_custom_schema_validation_lax() { json.validate_schema(JsonFile::LAX_SCHEMA, Some(&schema)) .unwrap(); - - std::fs::remove_file(&file).unwrap(); - std::fs::remove_file(&schema).unwrap(); } #[test] -#[ignore] fn test_custom_schema_validation_strict() { - let file = create_temp_file(); + let file_tmp = create_temp_file(); + let file = file_tmp.path().to_str().unwrap().to_string(); std::fs::write(&file, b"{ \"custom\": \"property\" }").unwrap(); - let schema = create_temp_file(); + let schema_tmp = create_temp_file(); + let schema = schema_tmp.path().to_str().unwrap().to_string(); std::fs::write( &schema, b"{ \"properties\": { \"custom\": { \"type\": \"string\" }}}", @@ -449,17 +431,16 @@ fn test_custom_schema_validation_strict() { json.validate_schema(JsonFile::STRICT_SCHEMA, Some(&schema)) .unwrap(); - - std::fs::remove_file(&file).unwrap(); - std::fs::remove_file(&schema).unwrap(); } #[test] -#[ignore] fn test_auth_schema_validation_with_custom_data_source() { + // WORDING NOTE: upstream asserts justinrainbow's "github-oauth : String value found, but an + // object is required". The jsonschema crate reports the same type violation with different + // wording. let json = shirabe_php_shim::json_decode("{\"github-oauth\": \"foo\"}", false).unwrap(); let expected_message = "\"COMPOSER_AUTH\" does not match the expected JSON schema".to_string(); - let expected_error = "github-oauth : String value found, but an object is required".to_string(); + let expected_error = "github-oauth : \"foo\" is not of type \"object\"".to_string(); let err = JsonFile::validate_json_schema("COMPOSER_AUTH", &json, JsonFile::AUTH_SCHEMA, None) .unwrap_err(); |
