aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock288
-rw-r--r--Cargo.toml1
-rw-r--r--crates/shirabe-external-packages/src/json_schema/mod.rs3
-rw-r--r--crates/shirabe-external-packages/src/json_schema/validator.rs30
-rw-r--r--crates/shirabe-external-packages/src/lib.rs1
-rw-r--r--crates/shirabe-php-shim/src/fs.rs6
-rw-r--r--crates/shirabe/Cargo.toml1
-rw-r--r--crates/shirabe/src/json/json_file.rs52
-rw-r--r--crates/shirabe/tests/json/composer_schema_test.rs135
-rw-r--r--crates/shirabe/tests/json/json_file_test.rs99
10 files changed, 500 insertions, 116 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9b9f3fa..6699db3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index f3f6d57..ae264d7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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();