aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--crates/shirabe-php-shim/src/json.rs21
-rw-r--r--crates/shirabe-php-shim/src/runtime.rs1
-rw-r--r--crates/shirabe/src/command/config_command.rs2
-rw-r--r--crates/shirabe/src/io/base_io.rs3
-rw-r--r--crates/shirabe/src/json/json_file.rs79
-rw-r--r--crates/shirabe/tests/json/json_file_test.rs20
-rw-r--r--docs/known-incompatibilities.md9
7 files changed, 54 insertions, 81 deletions
diff --git a/crates/shirabe-php-shim/src/json.rs b/crates/shirabe-php-shim/src/json.rs
index ed704f9..23c4c41 100644
--- a/crates/shirabe-php-shim/src/json.rs
+++ b/crates/shirabe-php-shim/src/json.rs
@@ -17,29 +17,22 @@ pub const JSON_PRETTY_PRINT: i64 = 128;
pub const JSON_THROW_ON_ERROR: i64 = 4194304;
pub const JSON_INVALID_UTF8_IGNORE: i64 = 1048576;
-pub const JSON_ERROR_NONE: i64 = 0;
-pub const JSON_ERROR_DEPTH: i64 = 1;
-pub const JSON_ERROR_STATE_MISMATCH: i64 = 2;
-pub const JSON_ERROR_CTRL_CHAR: i64 = 3;
-pub const JSON_ERROR_UTF8: i64 = 5;
-
-pub fn json_last_error() -> i64 {
- todo!()
-}
-
-pub fn json_encode<T: serde::Serialize + ?Sized>(value: &T) -> Option<String> {
+pub fn json_encode<T: serde::Serialize + ?Sized>(value: &T) -> anyhow::Result<String> {
// PHP's json_encode() with no flags escapes slashes and non-ASCII characters.
json_encode_ex(value, 0)
}
-pub fn json_encode_ex<T: serde::Serialize + ?Sized>(value: &T, flags: i64) -> Option<String> {
+pub fn json_encode_ex<T: serde::Serialize + ?Sized>(
+ value: &T,
+ flags: i64,
+) -> anyhow::Result<String> {
// serde_json's compact output already matches PHP's `json_encode` with both
// JSON_UNESCAPED_SLASHES and JSON_UNESCAPED_UNICODE set: forward slashes and non-ASCII
// characters are emitted verbatim. The two flags below re-apply PHP's default escaping when
// they are absent.
// TODO(phase-c): other flags (e.g. JSON_PRETTY_PRINT, JSON_HEX_*, JSON_THROW_ON_ERROR) are not
// handled yet; add them when a call site needs them.
- let mut s = serde_json::to_string(value).ok()?;
+ let mut s = serde_json::to_string(value)?;
if flags & JSON_UNESCAPED_SLASHES == 0 {
s = s.replace('/', "\\/");
@@ -60,7 +53,7 @@ pub fn json_encode_ex<T: serde::Serialize + ?Sized>(value: &T, flags: i64) -> Op
s = out;
}
- Some(s)
+ Ok(s)
}
// PHP's two-argument `json_decode`: without JSON_THROW_ON_ERROR it never throws,
diff --git a/crates/shirabe-php-shim/src/runtime.rs b/crates/shirabe-php-shim/src/runtime.rs
index cc63af6..2f98891 100644
--- a/crates/shirabe-php-shim/src/runtime.rs
+++ b/crates/shirabe-php-shim/src/runtime.rs
@@ -61,7 +61,6 @@ pub fn defined(name: &str) -> bool {
| "CURL_VERSION_LIBZ"
| "CURL_VERSION_ZSTD"
| "GLOB_BRACE"
- | "JSON_ERROR_UTF8"
| "OPENSSL_VERSION_TEXT"
| "PHP_BINARY"
| "SIGINT"
diff --git a/crates/shirabe/src/command/config_command.rs b/crates/shirabe/src/command/config_command.rs
index f6dffb7..1517e57 100644
--- a/crates/shirabe/src/command/config_command.rs
+++ b/crates/shirabe/src/command/config_command.rs
@@ -1332,7 +1332,7 @@ impl ConfigCommand {
return Err(RuntimeException {
message: format!(
"{} is an invalid value{}",
- PhpMixed::from(json_encode(&values_mixed)),
+ PhpMixed::from(json_encode(&values_mixed).ok()),
suffix
),
code: 0,
diff --git a/crates/shirabe/src/io/base_io.rs b/crates/shirabe/src/io/base_io.rs
index 99c3d70..79fd7b7 100644
--- a/crates/shirabe/src/io/base_io.rs
+++ b/crates/shirabe/src/io/base_io.rs
@@ -451,7 +451,8 @@ pub trait BaseIO: IOInterface {
Ok(json_encode_ex(
&PhpMixed::Array(log_context(context)),
JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
- ))
+ )
+ .ok())
});
if let Ok(Some(json_str)) = json {
message_str += " ";
diff --git a/crates/shirabe/src/json/json_file.rs b/crates/shirabe/src/json/json_file.rs
index 167a163..4145385 100644
--- a/crates/shirabe/src/json/json_file.rs
+++ b/crates/shirabe/src/json/json_file.rs
@@ -9,12 +9,10 @@ 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::{
- InvalidArgumentException, JSON_ERROR_CTRL_CHAR, JSON_ERROR_DEPTH, JSON_ERROR_NONE,
- JSON_ERROR_STATE_MISMATCH, JSON_ERROR_UTF8, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES,
- JSON_UNESCAPED_UNICODE, PhpMixed, RuntimeException, UnexpectedValueException, defined, dirname,
- file_exists, file_get_contents, file_put_contents, is_dir, is_file, json_decode,
- json_encode_ex, json_last_error, mkdir, php_dir, realpath, str_contains, str_ends_with,
- str_repeat, strlen, strpos, usleep,
+ InvalidArgumentException, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE,
+ PhpMixed, RuntimeException, UnexpectedValueException, dirname, file_exists, file_get_contents,
+ file_put_contents, is_dir, is_file, json_decode, json_encode_ex, mkdir, php_dir, realpath,
+ str_contains, str_ends_with, str_repeat, strlen, strpos, usleep,
};
use crate::downloader::TransportException;
@@ -413,18 +411,12 @@ impl JsonFile {
data: &T,
options: JsonEncodeOptions,
) -> String {
- let json = json_encode_ex(data, options.to_flags());
-
- let json = match json {
- Some(j) => j,
- None => {
- // PHP: self::throwEncodeError(json_last_error()), which throws \RuntimeException.
- // TODO(phase-c): faithfully propagating this requires encode/encode_with_options to
- // return Result<String>, a signature change rippling across ~53 call sites.
- Self::throw_encode_error(json_last_error()).unwrap_or_default();
- String::new()
- }
- };
+ let json = json_encode_ex(data, options.to_flags())
+ .map_err(|err| RuntimeException {
+ message: format!("JSON encoding failed: {}", err),
+ code: 0,
+ })
+ .unwrap(); // TODO(phase-c): propagating an Err.
if options.pretty_print && options.indent != Self::INDENT_DEFAULT {
// Pretty printing and not using default indentation
@@ -449,31 +441,6 @@ impl JsonFile {
json
}
- /// Throws an exception according to a given code with a customized message
- ///
- /// @param int $code return code of json_last_error function
- /// @throws \RuntimeException
- /// @return never
- fn throw_encode_error(code: i64) -> Result<()> {
- let msg = if code == JSON_ERROR_DEPTH {
- "Maximum stack depth exceeded"
- } else if code == JSON_ERROR_STATE_MISMATCH {
- "Underflow or the modes mismatch"
- } else if code == JSON_ERROR_CTRL_CHAR {
- "Unexpected control character found"
- } else if code == JSON_ERROR_UTF8 {
- "Malformed UTF-8 characters, possibly incorrectly encoded"
- } else {
- "Unknown error"
- };
-
- Err(RuntimeException {
- message: format!("JSON encoding failed: {}", msg),
- code: 0,
- }
- .into())
- }
-
/// Parses json string and returns hash.
///
/// @param null|string $json json string
@@ -487,7 +454,11 @@ impl JsonFile {
Some(j) => j,
};
let mut data = json_decode(json, true)?;
- if matches!(data, PhpMixed::Null) && JSON_ERROR_NONE != json_last_error() {
+ // PHP: `null === $data && JSON_ERROR_NONE !== json_last_error()`, i.e. the decode produced
+ // null because of an error rather than because the input was the literal `null`. json_decode
+ // here swallows the error into PhpMixed::Null, so detect the failure by comparing the source
+ // against `null`, mirroring validateSchema's own `'null' !== $content` check.
+ if matches!(data, PhpMixed::Null) && json != "null" {
// attempt resolving simple conflicts in lock files so that one can run `composer update --lock` and get a valid lock file
if let Some(file) = file
&& str_ends_with(file, ".lock")
@@ -524,16 +495,16 @@ impl JsonFile {
let mut parser = JsonParser::new();
let result = parser.lint(json);
if result.is_none() {
- if defined("JSON_ERROR_UTF8") && JSON_ERROR_UTF8 == json_last_error() {
- return Err(UnexpectedValueException {
- message: match file {
- None => "The input is not UTF-8, could not parse as JSON".to_string(),
- Some(f) => format!("\"{}\" is not UTF-8, could not parse as JSON", f),
- },
- code: 0,
- }
- .into());
- }
+ // TODO(phase-c): Rust's &str is guaranteed as UTF-8, but PHP string is not. Change `json`
+ // to &[u8] and check UTF-8 validity here.
+
+ // if (defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === json_last_error()) {
+ // if ($file === null) {
+ // throw new \UnexpectedValueException('The input is not UTF-8, could not parse as JSON');
+ // } else {
+ // throw new \UnexpectedValueException('"' . $file . '" is not UTF-8, could not parse as JSON');
+ // }
+ // }
return Ok(true);
}
diff --git a/crates/shirabe/tests/json/json_file_test.rs b/crates/shirabe/tests/json/json_file_test.rs
index 36e1136..b6fecc6 100644
--- a/crates/shirabe/tests/json/json_file_test.rs
+++ b/crates/shirabe/tests/json/json_file_test.rs
@@ -10,70 +10,70 @@ fn expect_parse_exception(text: &str, json: &str) {
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches json_last_error (todo!()) in the php-shim"]
+#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
fn test_parse_error_detect_extra_comma() {
let json = "{\n \"foo\": \"bar\",\n}";
expect_parse_exception("Parse error on line 2", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches json_last_error (todo!()) in the php-shim"]
+#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
fn test_parse_error_detect_extra_comma_in_array() {
let json = "{\n \"foo\": [\n \"bar\",\n ]\n}";
expect_parse_exception("Parse error on line 3", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches json_last_error (todo!()) in the php-shim"]
+#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
fn test_parse_error_detect_unescaped_backslash() {
let json = "{\n \"fo\\o\": \"bar\"\n}";
expect_parse_exception("Parse error on line 1", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches json_last_error (todo!()) in the php-shim"]
+#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
fn test_parse_error_skips_escaped_backslash() {
let json = "{\n \"fo\\\\o\": \"bar\"\n \"a\": \"b\"\n}";
expect_parse_exception("Parse error on line 2", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches json_last_error (todo!()) in the php-shim"]
+#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
fn test_parse_error_detect_single_quotes() {
let json = "{\n 'foo': \"bar\"\n}";
expect_parse_exception("Parse error on line 1", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches json_last_error (todo!()) in the php-shim"]
+#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
fn test_parse_error_detect_missing_quotes() {
let json = "{\n foo: \"bar\"\n}";
expect_parse_exception("Parse error on line 1", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches json_last_error (todo!()) in the php-shim"]
+#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
fn test_parse_error_detect_array_as_hash() {
let json = "{\n \"foo\": [\"bar\": \"baz\"]\n}";
expect_parse_exception("Parse error on line 2", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches json_last_error (todo!()) in the php-shim"]
+#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
fn test_parse_error_detect_missing_comma() {
let json = "{\n \"foo\": \"bar\"\n \"bar\": \"foo\"\n}";
expect_parse_exception("Parse error on line 2", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches json_last_error (todo!()) in the php-shim"]
+#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
fn test_parse_error_detect_missing_comma_multiline() {
let json = "{\n \"foo\": \"barbar\"\n\n \"bar\": \"foo\"\n}";
expect_parse_exception("Parse error on line 2", json);
}
#[test]
-#[ignore = "JsonFile::parse_json error path reaches json_last_error (todo!()) in the php-shim"]
+#[ignore = "JsonFile::parse_json error path reaches JsonParser::new()/lint() (todo!()); the JSON linter is not yet ported"]
fn test_parse_error_detect_missing_colon() {
let json = "{\n \"foo\": \"bar\",\n \"bar\" \"foo\"\n}";
expect_parse_exception("Parse error on line 3", json);
diff --git a/docs/known-incompatibilities.md b/docs/known-incompatibilities.md
new file mode 100644
index 0000000..6f043ca
--- /dev/null
+++ b/docs/known-incompatibilities.md
@@ -0,0 +1,9 @@
+# Known Incompatibilities
+
+NOTE: This is not an exhaustive list. Shirabe is in early development and there are still a number of significant incompatibilities with Composer that are not documented here yet.
+
+
+## Error Messages
+
+Error messages, in particular those from PHP built-in functions, are not intended to be mapped exactly.
+Plugins or external tools that rely on error messages may break.