mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
json5
This commit is contained in:
parent
074e62951f
commit
cd63b5c9c0
84 changed files with 743 additions and 288 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
|
@ -1318,12 +1318,13 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonc-parser"
|
||||
version = "0.27.1"
|
||||
name = "json5"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01958dcb05b69d9612853b47df8f7881810e4f61b5cedd8894be04291f28ccb9"
|
||||
checksum = "56c86c72f9e1d3fe29baa32cab8896548eef9aae271fce4e796d16b583fdf6d5"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"serde",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2196,7 +2197,7 @@ dependencies = [
|
|||
"indexmap 2.13.0",
|
||||
"insta",
|
||||
"jod-thread",
|
||||
"jsonc-parser",
|
||||
"json5",
|
||||
"log",
|
||||
"maplit",
|
||||
"memofs",
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ ritz = "0.1.0"
|
|||
roblox_install = "1.0.0"
|
||||
serde = { version = "1.0.228", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.149"
|
||||
jsonc-parser = { version = "0.27.1", features = ["serde"] }
|
||||
json5 = "1.3.0"
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
toml = "0.5.11"
|
||||
termcolor = "1.4.1"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
source: tests/rojo_test/syncback_util.rs
|
||||
expression: "String::from_utf8_lossy(&output.stdout)"
|
||||
---
|
||||
Writing src/ignored_part.model.json
|
||||
Writing src/int_value.model.json
|
||||
Writing src/ignored_part.model.json5
|
||||
Writing src/int_value.model.json5
|
||||
Writing src/subfolder/string_value.txt
|
||||
Writing src/subfolder
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
source: tests/rojo_test/syncback_util.rs
|
||||
expression: "String::from_utf8_lossy(&output.stdout)"
|
||||
---
|
||||
Writing src/Message.model.json
|
||||
Writing src/Message.model.json5
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
source: tests/rojo_test/syncback_util.rs
|
||||
expression: "String::from_utf8_lossy(&output.stdout)"
|
||||
---
|
||||
Writing src/dir_with_meta/init.meta.json
|
||||
Writing src/dir_with_meta/init.meta.json5
|
||||
Writing src/model_json.model.json
|
||||
Writing src/project_json.project.json
|
||||
Writing src/dir_with_meta
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@
|
|||
source: tests/rojo_test/syncback_util.rs
|
||||
expression: "String::from_utf8_lossy(&output.stdout)"
|
||||
---
|
||||
Writing src/pointer.model.json
|
||||
Writing src/pointer.model.json5
|
||||
Writing src/target.txt
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
source: tests/rojo_test/syncback_util.rs
|
||||
expression: "String::from_utf8_lossy(&output.stdout)"
|
||||
---
|
||||
Writing src/Pointer1.model.json
|
||||
Writing src/Pointer2.model.json
|
||||
Writing src/Pointer3.model.json
|
||||
Writing src/Pointer1.model.json5
|
||||
Writing src/Pointer2.model.json5
|
||||
Writing src/Pointer3.model.json5
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ expression: "String::from_utf8_lossy(&output.stdout)"
|
|||
---
|
||||
Writing default.project.json
|
||||
Writing src/adjacent.luau
|
||||
Writing src/adjacent.meta.json
|
||||
Writing src/init/init.meta.json
|
||||
Writing src/adjacent.meta.json5
|
||||
Writing src/init/init.meta.json5
|
||||
Writing src/model.model.json
|
||||
Writing src/init
|
||||
|
|
|
|||
|
|
@ -3,23 +3,23 @@ source: tests/tests/syncback.rs
|
|||
expression: container.model.json
|
||||
---
|
||||
{
|
||||
"children": [
|
||||
children: [
|
||||
{
|
||||
"attributes": {
|
||||
"Rojo_Id": "value_1",
|
||||
"Rojo_Ref_Value": "ReplicatedStorage/container/value_1"
|
||||
attributes: {
|
||||
Rojo_Id: "value_1",
|
||||
Rojo_Ref_Value: "ReplicatedStorage/container/value_1",
|
||||
},
|
||||
"className": "ObjectValue",
|
||||
"name": "value_1"
|
||||
className: "ObjectValue",
|
||||
name: "value_1",
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"Rojo_Id": "value_1",
|
||||
"Rojo_Ref_Value": "ReplicatedStorage/container/value_2"
|
||||
attributes: {
|
||||
Rojo_Id: "value_1",
|
||||
Rojo_Ref_Value: "ReplicatedStorage/container/value_2",
|
||||
},
|
||||
"className": "ObjectValue",
|
||||
"name": "value_2"
|
||||
}
|
||||
className: "ObjectValue",
|
||||
name: "value_2",
|
||||
},
|
||||
],
|
||||
"className": "Folder"
|
||||
className: "Folder",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/int_value.model.json
|
||||
---
|
||||
{
|
||||
"className": "IntValue",
|
||||
"properties": {
|
||||
"Value": 1337.0
|
||||
}
|
||||
className: "IntValue",
|
||||
properties: {
|
||||
Value: 1337,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
source: tests/tests/syncback.rs
|
||||
expression: src/int_value.model.json5
|
||||
---
|
||||
{
|
||||
className: "IntValue",
|
||||
properties: {
|
||||
Value: 1337,
|
||||
},
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/Message.model.json
|
||||
---
|
||||
{
|
||||
"className": "Message",
|
||||
"properties": {
|
||||
"Text": "This message should be written to the disk."
|
||||
}
|
||||
className: "Message",
|
||||
properties: {
|
||||
Text: "This message should be written to the disk.",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
source: tests/tests/syncback.rs
|
||||
expression: src/Message.model.json5
|
||||
---
|
||||
{
|
||||
className: "Message",
|
||||
properties: {
|
||||
Text: "This message should be written to the disk.",
|
||||
},
|
||||
}
|
||||
|
|
@ -3,10 +3,5 @@ source: tests/tests/syncback.rs
|
|||
expression: src/dir_with_meta/init.meta.json
|
||||
---
|
||||
{
|
||||
"className": "Configuration",
|
||||
"properties": {
|
||||
"Tags": [
|
||||
"This tag on dir_with_meta"
|
||||
]
|
||||
}
|
||||
"className": "Configuration"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
source: tests/tests/syncback.rs
|
||||
expression: src/dir_with_meta/init.meta.json5
|
||||
---
|
||||
{
|
||||
properties: {
|
||||
Tags: [
|
||||
"This tag on dir_with_meta",
|
||||
],
|
||||
},
|
||||
className: "Configuration",
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/model_json.model.json
|
||||
---
|
||||
{
|
||||
"className": "StringValue",
|
||||
"properties": {
|
||||
"Value": "This text is model_json"
|
||||
}
|
||||
className: "StringValue",
|
||||
properties: {
|
||||
Value: "This text is model_json",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ source: tests/tests/syncback.rs
|
|||
expression: src/project_json.project.json
|
||||
---
|
||||
{
|
||||
"name": "project_json",
|
||||
"tree": {
|
||||
"$className": "Color3Value",
|
||||
"$properties": {
|
||||
"Value": [
|
||||
1337.0,
|
||||
-1337.0,
|
||||
1337.0
|
||||
]
|
||||
}
|
||||
}
|
||||
name: "project_json",
|
||||
tree: {
|
||||
$className: "Color3Value",
|
||||
$properties: {
|
||||
Value: [
|
||||
1337,
|
||||
-1337,
|
||||
1337,
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@ source: tests/tests/syncback.rs
|
|||
expression: nested.project.json
|
||||
---
|
||||
{
|
||||
"name": "Nested",
|
||||
"tree": {
|
||||
"$className": "Configuration",
|
||||
"BoolValue": {
|
||||
"$className": "BoolValue",
|
||||
"$properties": {
|
||||
"Value": true
|
||||
}
|
||||
name: "Nested",
|
||||
tree: {
|
||||
$className: "Configuration",
|
||||
BoolValue: {
|
||||
$className: "BoolValue",
|
||||
$properties: {
|
||||
Value: true,
|
||||
},
|
||||
},
|
||||
"StringValue": {
|
||||
"$path": "string_value.txt"
|
||||
}
|
||||
}
|
||||
StringValue: {
|
||||
$path: "string_value.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ source: tests/tests/syncback.rs
|
|||
expression: property_mismatch.project.json
|
||||
---
|
||||
{
|
||||
"name": "property_mismatch",
|
||||
"tree": {
|
||||
"$className": "BrickColorValue",
|
||||
"$properties": {
|
||||
"Value": {
|
||||
"BrickColor": 345
|
||||
}
|
||||
}
|
||||
}
|
||||
name: "property_mismatch",
|
||||
tree: {
|
||||
$className: "BrickColorValue",
|
||||
$properties: {
|
||||
Value: {
|
||||
BrickColor: 345,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/pointer.model.json
|
||||
---
|
||||
{
|
||||
"attributes": {
|
||||
"Rojo_Ref_Value": "ReplicatedStorage/target"
|
||||
attributes: {
|
||||
Rojo_Ref_Value: "ReplicatedStorage/target",
|
||||
},
|
||||
"className": "ObjectValue"
|
||||
className: "ObjectValue",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/target.model.json
|
||||
---
|
||||
{
|
||||
"attributes": {
|
||||
"Rojo_Id": "test referent id"
|
||||
attributes: {
|
||||
Rojo_Id: "test referent id",
|
||||
},
|
||||
"className": "Folder"
|
||||
className: "Folder",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/pointer.model.json
|
||||
---
|
||||
{
|
||||
"attributes": {
|
||||
"Rojo_Ref_Value": "ReplicatedStorage/target"
|
||||
className: "ObjectValue",
|
||||
attributes: {
|
||||
Rojo_Ref_Value: "ReplicatedStorage/target",
|
||||
},
|
||||
"className": "ObjectValue"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
source: tests/tests/syncback.rs
|
||||
expression: src/pointer.model.json5
|
||||
---
|
||||
{
|
||||
attributes: {
|
||||
Rojo_Ref_Value: "ReplicatedStorage/target",
|
||||
},
|
||||
className: "ObjectValue",
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/Pointer_2.model.json
|
||||
---
|
||||
{
|
||||
"attributes": {
|
||||
"Rojo_Ref_Value": "ReplicatedStorage/Target_2"
|
||||
attributes: {
|
||||
Rojo_Ref_Value: "ReplicatedStorage/Target_2",
|
||||
},
|
||||
"className": "ObjectValue"
|
||||
className: "ObjectValue",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/Target_2.model.json
|
||||
---
|
||||
{
|
||||
"attributes": {
|
||||
"Rojo_Id": "identical ID"
|
||||
attributes: {
|
||||
Rojo_Id: "identical ID",
|
||||
},
|
||||
"className": "Folder"
|
||||
className: "Folder",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/Pointer1.model.json
|
||||
---
|
||||
{
|
||||
"attributes": {
|
||||
"Rojo_Ref_Value": "ReplicatedStorage/Target1"
|
||||
className: "ObjectValue",
|
||||
attributes: {
|
||||
Rojo_Ref_Value: "ReplicatedStorage/Target1",
|
||||
},
|
||||
"className": "ObjectValue"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
source: tests/tests/syncback.rs
|
||||
expression: src/Pointer1.model.json5
|
||||
---
|
||||
{
|
||||
attributes: {
|
||||
Rojo_Ref_Value: "ReplicatedStorage/Target1",
|
||||
},
|
||||
className: "ObjectValue",
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/Pointer2.model.json
|
||||
---
|
||||
{
|
||||
"attributes": {
|
||||
"Rojo_Ref_Value": "ReplicatedStorage/Target2"
|
||||
className: "ObjectValue",
|
||||
attributes: {
|
||||
Rojo_Ref_Value: "ReplicatedStorage/Target2",
|
||||
},
|
||||
"className": "ObjectValue"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
source: tests/tests/syncback.rs
|
||||
expression: src/Pointer2.model.json5
|
||||
---
|
||||
{
|
||||
attributes: {
|
||||
Rojo_Ref_Value: "ReplicatedStorage/Target2",
|
||||
},
|
||||
className: "ObjectValue",
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/Pointer3.model.json
|
||||
---
|
||||
{
|
||||
"attributes": {
|
||||
"Rojo_Ref_Value": "ReplicatedStorage/Target3"
|
||||
className: "ObjectValue",
|
||||
attributes: {
|
||||
Rojo_Ref_Value: "ReplicatedStorage/Target3",
|
||||
},
|
||||
"className": "ObjectValue"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
source: tests/tests/syncback.rs
|
||||
expression: src/Pointer3.model.json5
|
||||
---
|
||||
{
|
||||
attributes: {
|
||||
Rojo_Ref_Value: "ReplicatedStorage/Target3",
|
||||
},
|
||||
className: "ObjectValue",
|
||||
}
|
||||
|
|
@ -3,14 +3,14 @@ source: tests/tests/syncback.rs
|
|||
expression: default.project.json
|
||||
---
|
||||
{
|
||||
"name": "respect_old_middleware",
|
||||
"tree": {
|
||||
"$path": "src",
|
||||
"project_node": {
|
||||
"$className": "BoolValue",
|
||||
"$properties": {
|
||||
"Value": true
|
||||
}
|
||||
}
|
||||
}
|
||||
name: "respect_old_middleware",
|
||||
tree: {
|
||||
$path: "src",
|
||||
project_node: {
|
||||
$className: "BoolValue",
|
||||
$properties: {
|
||||
Value: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ source: tests/tests/syncback.rs
|
|||
expression: src/model_json.model.json
|
||||
---
|
||||
{
|
||||
"className": "StringValue",
|
||||
"properties": {
|
||||
"Value": "This should be a .model.json file"
|
||||
}
|
||||
className: "StringValue",
|
||||
properties: {
|
||||
Value: "This should be a .model.json file",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,18 @@ source: tests/tests/syncback.rs
|
|||
expression: default.project.json
|
||||
---
|
||||
{
|
||||
"$schema": "rojo/project-schema",
|
||||
"name": "schema_roundtrip",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"src": {
|
||||
"$path": "src"
|
||||
$schema: "rojo/project-schema",
|
||||
name: "schema_roundtrip",
|
||||
tree: {
|
||||
$className: "Folder",
|
||||
src: {
|
||||
$path: "src",
|
||||
},
|
||||
"trigger_reserialization": {
|
||||
"$className": "BoolValue",
|
||||
"$properties": {
|
||||
"Value": true
|
||||
}
|
||||
}
|
||||
}
|
||||
trigger_reserialization: {
|
||||
$className: "BoolValue",
|
||||
$properties: {
|
||||
Value: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,5 @@ source: tests/tests/syncback.rs
|
|||
expression: src/adjacent.meta.json
|
||||
---
|
||||
{
|
||||
"$schema": "rojo/adjacent-meta",
|
||||
"attributes": {
|
||||
"trigger_reserialization": true
|
||||
}
|
||||
"$schema": "rojo/adjacent-meta"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,5 @@ source: tests/tests/syncback.rs
|
|||
expression: src/init/init.meta.json
|
||||
---
|
||||
{
|
||||
"$schema": "rojo/init-meta",
|
||||
"attributes": {
|
||||
"trigger_reserialization": true
|
||||
}
|
||||
"$schema": "rojo/init-meta"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@ source: tests/tests/syncback.rs
|
|||
expression: src/model.model.json
|
||||
---
|
||||
{
|
||||
"$schema": "rojo/model.json",
|
||||
"className": "BoolValue"
|
||||
$schema: "rojo/model.json",
|
||||
className: "BoolValue",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,20 @@ source: tests/tests/syncback.rs
|
|||
expression: default.project.json
|
||||
---
|
||||
{
|
||||
"name": "string_value_project",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"inside_project_file": {
|
||||
"$className": "StringValue",
|
||||
"$properties": {
|
||||
"Value": "imgettingverytiredofwritingthesetests2"
|
||||
}
|
||||
},
|
||||
"on_file_system": {
|
||||
"$attributes": {
|
||||
"imgettingverytiredofwritingthesetests": "person299 was ahead of his time"
|
||||
name: "string_value_project",
|
||||
tree: {
|
||||
$className: "Folder",
|
||||
inside_project_file: {
|
||||
$className: "StringValue",
|
||||
$properties: {
|
||||
Value: "imgettingverytiredofwritingthesetests2",
|
||||
},
|
||||
"$path": "string_value.txt"
|
||||
}
|
||||
}
|
||||
},
|
||||
on_file_system: {
|
||||
$attributes: {
|
||||
imgettingverytiredofwritingthesetests: "person299 was ahead of his time",
|
||||
},
|
||||
$path: "string_value.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@ source: tests/tests/syncback.rs
|
|||
expression: default.project.json
|
||||
---
|
||||
{
|
||||
"name": "unscriptable_properties",
|
||||
"syncbackRules": {
|
||||
"syncUnscriptable": true
|
||||
name: "unscriptable_properties",
|
||||
syncbackRules: {
|
||||
syncUnscriptable: true,
|
||||
},
|
||||
tree: {
|
||||
$className: "BinaryStringValue",
|
||||
$properties: {
|
||||
Value: {
|
||||
BinaryString: "Rojo/is/cool",
|
||||
},
|
||||
},
|
||||
},
|
||||
"tree": {
|
||||
"$className": "BinaryStringValue",
|
||||
"$properties": {
|
||||
"Value": {
|
||||
"BinaryString": "Rojo/is/cool"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
469
src/json.rs
469
src/json.rs
|
|
@ -16,10 +16,9 @@ use serde::{de::DeserializeOwned, Serialize};
|
|||
/// Returns an error if:
|
||||
/// - The text is not valid JSONC
|
||||
/// - The text contains no JSON value
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_value(text: &str) -> anyhow::Result<serde_json::Value> {
|
||||
jsonc_parser::parse_to_serde_value(text, &Default::default())
|
||||
.context("Failed to parse JSONC")?
|
||||
.ok_or_else(|| anyhow::anyhow!("File contains no JSON value"))
|
||||
json5::from_str(text).context("Failed to parse JSON5")
|
||||
}
|
||||
|
||||
/// Parse JSONC text into a `serde_json::Value` with a custom context message.
|
||||
|
|
@ -36,9 +35,7 @@ pub fn parse_value_with_context(
|
|||
text: &str,
|
||||
context: impl Fn() -> String,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
jsonc_parser::parse_to_serde_value(text, &Default::default())
|
||||
.with_context(|| format!("{}: JSONC parse error", context()))?
|
||||
.ok_or_else(|| anyhow::anyhow!("{}: File contains no JSON value", context()))
|
||||
json5::from_str(text).with_context(|| format!("{}: JSON5 parse error", context()))
|
||||
}
|
||||
|
||||
/// Parse JSONC text and deserialize it into a specific type.
|
||||
|
|
@ -53,8 +50,7 @@ pub fn parse_value_with_context(
|
|||
/// - The text contains no JSON value
|
||||
/// - The value cannot be deserialized into type `T`
|
||||
pub fn from_str<T: DeserializeOwned>(text: &str) -> anyhow::Result<T> {
|
||||
let value = parse_value(text)?;
|
||||
serde_json::from_value(value).context("Failed to deserialize JSON")
|
||||
json5::from_str(text).context("Failed to deserialize JSON5")
|
||||
}
|
||||
|
||||
/// Parse JSONC text and deserialize it into a specific type with a custom context message.
|
||||
|
|
@ -72,8 +68,7 @@ pub fn from_str_with_context<T: DeserializeOwned>(
|
|||
text: &str,
|
||||
context: impl Fn() -> String,
|
||||
) -> anyhow::Result<T> {
|
||||
let value = parse_value_with_context(text, &context)?;
|
||||
serde_json::from_value(value).with_context(|| format!("{}: Invalid JSON structure", context()))
|
||||
json5::from_str(text).with_context(|| format!("{}: JSON5 parse error", context()))
|
||||
}
|
||||
|
||||
/// Parse JSONC bytes into a `serde_json::Value` with a custom context message.
|
||||
|
|
@ -131,39 +126,35 @@ pub fn from_slice_with_context<T: DeserializeOwned>(
|
|||
from_str_with_context(text, context)
|
||||
}
|
||||
|
||||
/// Recursively sorts all object keys in a JSON value for deterministic output.
|
||||
fn sort_json_value(value: serde_json::Value) -> serde_json::Value {
|
||||
match value {
|
||||
serde_json::Value::Object(map) => {
|
||||
let sorted: serde_json::Map<String, serde_json::Value> = map
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, sort_json_value(v)))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.collect::<std::collections::BTreeMap<_, _>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
serde_json::Value::Object(sorted)
|
||||
}
|
||||
serde_json::Value::Array(arr) => {
|
||||
serde_json::Value::Array(arr.into_iter().map(sort_json_value).collect())
|
||||
}
|
||||
other => other,
|
||||
}
|
||||
/// A JSON5 value that uses BTreeMap for sorted keys and supports NaN/Infinity.
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum Json5Value {
|
||||
Null,
|
||||
Bool(bool),
|
||||
Number(f64),
|
||||
String(String),
|
||||
Array(Vec<Json5Value>),
|
||||
Object(std::collections::BTreeMap<String, Json5Value>),
|
||||
}
|
||||
|
||||
/// Serialize a value to a pretty-printed JSON byte vector with sorted keys.
|
||||
/// Serialize a value to a JSON5 byte vector with sorted keys.
|
||||
///
|
||||
/// This ensures deterministic output by sorting all object keys alphabetically,
|
||||
/// which is especially useful for version control and syncback operations.
|
||||
/// Supports NaN/Infinity and sorts object keys alphabetically.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the value cannot be serialized to JSON.
|
||||
/// Returns an error if the value cannot be serialized to JSON5.
|
||||
pub fn to_vec_pretty_sorted<T: Serialize>(value: &T) -> anyhow::Result<Vec<u8>> {
|
||||
let json_value = serde_json::to_value(value).context("Failed to convert to JSON value")?;
|
||||
let sorted_value = sort_json_value(json_value);
|
||||
serde_json::to_vec_pretty(&sorted_value).context("Failed to serialize to JSON")
|
||||
// Serialize to json5 string (preserves NaN/Infinity)
|
||||
let json5_str = json5::to_string(value).context("Failed to serialize to JSON5")?;
|
||||
|
||||
// Parse into Json5Value which uses BTreeMap (sorted keys)
|
||||
let sorted: Json5Value = json5::from_str(&json5_str).context("Failed to parse JSON5 for sorting")?;
|
||||
|
||||
// Serialize back to json5 (now sorted)
|
||||
let output = json5::to_string(&sorted).context("Failed to serialize sorted JSON5")?;
|
||||
Ok(output.into_bytes())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -204,14 +195,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_parse_value_empty() {
|
||||
let err = parse_value("").unwrap_err();
|
||||
assert!(err.to_string().contains("no JSON value"));
|
||||
// Empty string should fail to parse
|
||||
assert!(parse_value("").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_value_invalid() {
|
||||
let err = parse_value("{invalid}").unwrap_err();
|
||||
assert!(err.to_string().contains("parse"));
|
||||
// Invalid JSON should fail to parse
|
||||
assert!(parse_value("{invalid}").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -271,7 +262,7 @@ mod tests {
|
|||
})
|
||||
.unwrap_err();
|
||||
assert!(err.to_string().contains("config.json"));
|
||||
assert!(err.to_string().contains("Invalid JSON structure"));
|
||||
assert!(err.to_string().contains("JSON5 parse error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -334,7 +325,7 @@ mod tests {
|
|||
})
|
||||
.unwrap_err();
|
||||
assert!(err.to_string().contains("config.json"));
|
||||
assert!(err.to_string().contains("Invalid JSON structure"));
|
||||
assert!(err.to_string().contains("JSON5 parse error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -347,7 +338,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_vec_pretty_sorted() {
|
||||
fn test_to_vec_pretty_json5_format() {
|
||||
use indexmap::IndexMap;
|
||||
|
||||
let mut map: IndexMap<String, i32> = IndexMap::new();
|
||||
|
|
@ -358,33 +349,389 @@ mod tests {
|
|||
let result = to_vec_pretty_sorted(&map).unwrap();
|
||||
let output = String::from_utf8(result).unwrap();
|
||||
|
||||
// Keys should be in alphabetical order
|
||||
let apple_pos = output.find("apple").unwrap();
|
||||
let mango_pos = output.find("mango").unwrap();
|
||||
let zebra_pos = output.find("zebra").unwrap();
|
||||
assert!(apple_pos < mango_pos);
|
||||
assert!(mango_pos < zebra_pos);
|
||||
// JSON5 output should contain all keys (may be unquoted)
|
||||
assert!(output.contains("zebra"));
|
||||
assert!(output.contains("apple"));
|
||||
assert!(output.contains("mango"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sorted_nested_objects() {
|
||||
fn test_serialize_special_floats() {
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Nested {
|
||||
inner: std::collections::HashMap<String, i32>,
|
||||
struct SpecialFloats {
|
||||
infinity: f64,
|
||||
neg_infinity: f64,
|
||||
nan: f64,
|
||||
normal: f64,
|
||||
}
|
||||
|
||||
let mut inner = std::collections::HashMap::new();
|
||||
inner.insert("z".to_string(), 1);
|
||||
inner.insert("a".to_string(), 2);
|
||||
let data = SpecialFloats {
|
||||
infinity: f64::INFINITY,
|
||||
neg_infinity: f64::NEG_INFINITY,
|
||||
nan: f64::NAN,
|
||||
normal: 42.5,
|
||||
};
|
||||
|
||||
let nested = Nested { inner };
|
||||
let output = String::from_utf8(to_vec_pretty_sorted(&nested).unwrap()).unwrap();
|
||||
let output = String::from_utf8(to_vec_pretty_sorted(&data).unwrap()).unwrap();
|
||||
|
||||
// Inner keys should also be sorted
|
||||
let a_pos = output.find("\"a\"").unwrap();
|
||||
let z_pos = output.find("\"z\"").unwrap();
|
||||
assert!(a_pos < z_pos);
|
||||
// JSON5 should output special float values correctly
|
||||
assert!(output.contains("Infinity"));
|
||||
assert!(output.contains("-Infinity"));
|
||||
assert!(output.contains("NaN"));
|
||||
assert!(output.contains("42.5"));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// JSON5 NaN/Infinity tests - critical for Roblox sync/syncback
|
||||
// =========================================================================
|
||||
//
|
||||
// Note: serde_json::Value cannot represent NaN/Infinity (as_f64 returns None).
|
||||
// However, direct deserialization into f64 fields works correctly.
|
||||
// This is fine for Rojo since properties are deserialized into typed structs.
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
struct Vector3 {
|
||||
x: f64,
|
||||
y: f64,
|
||||
z: f64,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_infinity() {
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TestFloat { x: f64 }
|
||||
let result: TestFloat = from_str(r#"{ "x": Infinity }"#).unwrap();
|
||||
assert!(result.x.is_infinite() && result.x.is_sign_positive());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_negative_infinity() {
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TestFloat { x: f64 }
|
||||
let result: TestFloat = from_str(r#"{ "x": -Infinity }"#).unwrap();
|
||||
assert!(result.x.is_infinite() && result.x.is_sign_negative());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_nan() {
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TestFloat { x: f64 }
|
||||
let result: TestFloat = from_str(r#"{ "x": NaN }"#).unwrap();
|
||||
assert!(result.x.is_nan());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_vector_with_infinity() {
|
||||
let result: Vector3 = from_str(r#"{ "x": Infinity, "y": -Infinity, "z": 0 }"#).unwrap();
|
||||
assert!(result.x.is_infinite() && result.x.is_sign_positive());
|
||||
assert!(result.y.is_infinite() && result.y.is_sign_negative());
|
||||
assert_eq!(result.z, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_vector_with_nan() {
|
||||
let result: Vector3 = from_str(r#"{ "x": NaN, "y": 1.5, "z": NaN }"#).unwrap();
|
||||
assert!(result.x.is_nan());
|
||||
assert_eq!(result.y, 1.5);
|
||||
assert!(result.z.is_nan());
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CFrameLike {
|
||||
position: Vector3,
|
||||
orientation: [f64; 9],
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_cframe_with_special_values() {
|
||||
// Simulates a CFrame with NaN/Infinity in position and rotation matrix
|
||||
let result: CFrameLike = from_str(
|
||||
r#"{
|
||||
"position": { "x": Infinity, "y": NaN, "z": -Infinity },
|
||||
"orientation": [1, 0, 0, 0, NaN, 0, 0, 0, Infinity]
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(result.position.x.is_infinite() && result.position.x.is_sign_positive());
|
||||
assert!(result.position.y.is_nan());
|
||||
assert!(result.position.z.is_infinite() && result.position.z.is_sign_negative());
|
||||
assert_eq!(result.orientation[0], 1.0);
|
||||
assert!(result.orientation[4].is_nan());
|
||||
assert!(result.orientation[8].is_infinite());
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TypedProperties {
|
||||
#[serde(rename = "Size")]
|
||||
size: Vector3,
|
||||
#[serde(rename = "Transparency")]
|
||||
transparency: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RojoPropertyTyped {
|
||||
#[serde(rename = "$className")]
|
||||
class_name: String,
|
||||
#[serde(rename = "$properties")]
|
||||
properties: TypedProperties,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_rojo_style_properties_with_special_floats() {
|
||||
// Simulates how Rojo project files might contain properties with NaN/Infinity
|
||||
let result: RojoPropertyTyped = from_str(
|
||||
r#"{
|
||||
"$className": "Part",
|
||||
"$properties": {
|
||||
"Size": { "x": Infinity, "y": NaN, "z": -Infinity },
|
||||
"Transparency": NaN
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.class_name, "Part");
|
||||
assert!(result.properties.size.x.is_infinite());
|
||||
assert!(result.properties.size.y.is_nan());
|
||||
assert!(result.properties.size.z.is_infinite());
|
||||
assert!(result.properties.transparency.is_nan());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json5_with_comments_and_special_floats() {
|
||||
// Combines JSON5 features: comments, trailing commas, and special float values
|
||||
let result: Vector3 = from_str(
|
||||
r#"{
|
||||
// Position with infinite X
|
||||
"x": Infinity,
|
||||
"y": -Infinity, // Negative infinity
|
||||
"z": NaN, // Not a number
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(result.x.is_infinite() && result.x.is_sign_positive());
|
||||
assert!(result.y.is_infinite() && result.y.is_sign_negative());
|
||||
assert!(result.z.is_nan());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_slice_with_special_floats() {
|
||||
let result: Vector3 =
|
||||
from_slice(br#"{ "x": Infinity, "y": NaN, "z": -Infinity }"#).unwrap();
|
||||
assert!(result.x.is_infinite());
|
||||
assert!(result.y.is_nan());
|
||||
assert!(result.z.is_infinite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_array_of_special_floats() {
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ArrayHolder {
|
||||
values: Vec<f64>,
|
||||
}
|
||||
let result: ArrayHolder =
|
||||
from_str(r#"{ "values": [1.0, Infinity, -Infinity, NaN, 0.0] }"#).unwrap();
|
||||
assert_eq!(result.values[0], 1.0);
|
||||
assert!(result.values[1].is_infinite() && result.values[1].is_sign_positive());
|
||||
assert!(result.values[2].is_infinite() && result.values[2].is_sign_negative());
|
||||
assert!(result.values[3].is_nan());
|
||||
assert_eq!(result.values[4], 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_nested_special_floats() {
|
||||
// Simulates nested structures like CFrame matrices
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Transform {
|
||||
position: [f64; 3],
|
||||
rotation: [[f64; 3]; 3],
|
||||
}
|
||||
let result: Transform = from_str(
|
||||
r#"{
|
||||
"position": [Infinity, NaN, -Infinity],
|
||||
"rotation": [
|
||||
[1, 0, NaN],
|
||||
[0, Infinity, 0],
|
||||
[-Infinity, 0, 1]
|
||||
]
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(result.position[0].is_infinite());
|
||||
assert!(result.position[1].is_nan());
|
||||
assert!(result.position[2].is_infinite());
|
||||
assert!(result.rotation[0][2].is_nan());
|
||||
assert!(result.rotation[1][1].is_infinite());
|
||||
assert!(result.rotation[2][0].is_infinite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixed_json5_features_with_special_floats() {
|
||||
// All JSON5 features together: unquoted keys would work too but we use quoted for Rojo
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct MixedTest {
|
||||
normal: f64,
|
||||
infinity: f64,
|
||||
negative_infinity: f64,
|
||||
nan_value: f64,
|
||||
}
|
||||
let result: MixedTest = from_str(
|
||||
r#"{
|
||||
// Regular number
|
||||
"normal": 42.5,
|
||||
"infinity": Infinity, // Positive infinity
|
||||
"negative_infinity": -Infinity,
|
||||
"nan_value": NaN, // Not a number
|
||||
}"#, // trailing comma
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.normal, 42.5);
|
||||
assert!(result.infinity.is_infinite() && result.infinity.is_sign_positive());
|
||||
assert!(result.negative_infinity.is_infinite() && result.negative_infinity.is_sign_negative());
|
||||
assert!(result.nan_value.is_nan());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Syncback roundtrip tests - serialize then deserialize
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn test_syncback_roundtrip_special_floats() {
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
struct SyncbackData {
|
||||
normal: f64,
|
||||
infinity: f64,
|
||||
neg_infinity: f64,
|
||||
}
|
||||
|
||||
let original = SyncbackData {
|
||||
normal: 42.5,
|
||||
infinity: f64::INFINITY,
|
||||
neg_infinity: f64::NEG_INFINITY,
|
||||
};
|
||||
|
||||
// Serialize with json5
|
||||
let serialized = to_vec_pretty_sorted(&original).unwrap();
|
||||
let json_str = String::from_utf8(serialized).unwrap();
|
||||
|
||||
// Verify JSON5 output contains special values
|
||||
assert!(json_str.contains("Infinity"));
|
||||
assert!(json_str.contains("-Infinity"));
|
||||
|
||||
// Deserialize back
|
||||
let deserialized: SyncbackData = from_str(&json_str).unwrap();
|
||||
|
||||
assert_eq!(deserialized.normal, original.normal);
|
||||
assert!(deserialized.infinity.is_infinite() && deserialized.infinity.is_sign_positive());
|
||||
assert!(deserialized.neg_infinity.is_infinite() && deserialized.neg_infinity.is_sign_negative());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syncback_roundtrip_nan() {
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct NanData {
|
||||
value: f64,
|
||||
}
|
||||
|
||||
let original = NanData { value: f64::NAN };
|
||||
|
||||
// Serialize with json5
|
||||
let serialized = to_vec_pretty_sorted(&original).unwrap();
|
||||
let json_str = String::from_utf8(serialized).unwrap();
|
||||
|
||||
// Verify JSON5 output contains NaN
|
||||
assert!(json_str.contains("NaN"));
|
||||
|
||||
// Deserialize back
|
||||
let deserialized: NanData = from_str(&json_str).unwrap();
|
||||
assert!(deserialized.value.is_nan());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syncback_cframe_like_roundtrip() {
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct CFrameData {
|
||||
position: [f64; 3],
|
||||
orientation: [[f64; 3]; 3],
|
||||
}
|
||||
|
||||
let original = CFrameData {
|
||||
position: [f64::INFINITY, f64::NAN, f64::NEG_INFINITY],
|
||||
orientation: [
|
||||
[1.0, 0.0, f64::NAN],
|
||||
[0.0, f64::INFINITY, 0.0],
|
||||
[f64::NEG_INFINITY, 0.0, 1.0],
|
||||
],
|
||||
};
|
||||
|
||||
// Serialize
|
||||
let serialized = to_vec_pretty_sorted(&original).unwrap();
|
||||
let json_str = String::from_utf8(serialized).unwrap();
|
||||
|
||||
// Deserialize
|
||||
let deserialized: CFrameData = from_str(&json_str).unwrap();
|
||||
|
||||
// Verify position
|
||||
assert!(deserialized.position[0].is_infinite() && deserialized.position[0].is_sign_positive());
|
||||
assert!(deserialized.position[1].is_nan());
|
||||
assert!(deserialized.position[2].is_infinite() && deserialized.position[2].is_sign_negative());
|
||||
|
||||
// Verify orientation
|
||||
assert_eq!(deserialized.orientation[0][0], 1.0);
|
||||
assert!(deserialized.orientation[0][2].is_nan());
|
||||
assert!(deserialized.orientation[1][1].is_infinite());
|
||||
assert!(deserialized.orientation[2][0].is_infinite() && deserialized.orientation[2][0].is_sign_negative());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syncback_rojo_meta_like_roundtrip() {
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct MetaFile {
|
||||
#[serde(rename = "className")]
|
||||
class_name: String,
|
||||
properties: BTreeMap<String, f64>,
|
||||
}
|
||||
|
||||
let mut properties = BTreeMap::new();
|
||||
properties.insert("Transparency".to_string(), f64::NAN);
|
||||
properties.insert("Health".to_string(), f64::INFINITY);
|
||||
properties.insert("WalkSpeed".to_string(), 16.0);
|
||||
|
||||
let original = MetaFile {
|
||||
class_name: "Humanoid".to_string(),
|
||||
properties,
|
||||
};
|
||||
|
||||
// Serialize
|
||||
let serialized = to_vec_pretty_sorted(&original).unwrap();
|
||||
let json_str = String::from_utf8(serialized).unwrap();
|
||||
|
||||
// Verify output format
|
||||
assert!(json_str.contains("Humanoid"));
|
||||
assert!(json_str.contains("NaN"));
|
||||
assert!(json_str.contains("Infinity"));
|
||||
|
||||
// Deserialize
|
||||
let deserialized: MetaFile = from_str(&json_str).unwrap();
|
||||
|
||||
assert_eq!(deserialized.class_name, "Humanoid");
|
||||
assert!(deserialized.properties.get("Transparency").unwrap().is_nan());
|
||||
assert!(deserialized.properties.get("Health").unwrap().is_infinite());
|
||||
assert_eq!(*deserialized.properties.get("WalkSpeed").unwrap(), 16.0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use crate::{
|
|||
};
|
||||
|
||||
/// Represents 'default' project names that act as `init` files
|
||||
pub static DEFAULT_PROJECT_NAMES: [&str; 2] = ["default.project.json", "default.project.jsonc"];
|
||||
pub static DEFAULT_PROJECT_NAMES: [&str; 3] = ["default.project.json", "default.project.jsonc", "default.project.json5"];
|
||||
|
||||
/// Error type returned by any function that handles projects.
|
||||
#[derive(Debug, Error)]
|
||||
|
|
@ -48,7 +48,8 @@ enum Error {
|
|||
|
||||
#[error("Error parsing Rojo project in path {}", .path.display())]
|
||||
Json {
|
||||
source: serde_json::Error,
|
||||
#[source]
|
||||
source: anyhow::Error,
|
||||
path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
|
@ -145,7 +146,7 @@ impl Project {
|
|||
pub fn is_project_file(path: &Path) -> bool {
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.map(|name| name.ends_with(".project.json") || name.ends_with(".project.jsonc"))
|
||||
.map(|name| name.ends_with(".project.json") || name.ends_with(".project.jsonc") || name.ends_with(".project.json5"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
|
|
@ -234,10 +235,7 @@ impl Project {
|
|||
fallback_name: Option<&str>,
|
||||
) -> Result<Self, Error> {
|
||||
let mut project: Self = json::from_slice(contents).map_err(|e| Error::Json {
|
||||
source: serde_json::Error::io(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
e.to_string(),
|
||||
)),
|
||||
source: e,
|
||||
path: project_file_location.clone(),
|
||||
})?;
|
||||
project.file_location = project_file_location;
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ pub fn syncback_csv<'sync>(
|
|||
new_inst.name.clone()
|
||||
};
|
||||
fs_snapshot.add_file(
|
||||
parent.join(format!("{}.meta.json", meta_name)),
|
||||
parent.join(format!("{}.meta.json5", meta_name)),
|
||||
crate::json::to_vec_pretty_sorted(&meta).context("cannot serialize metadata")?,
|
||||
)
|
||||
}
|
||||
|
|
@ -154,9 +154,9 @@ pub fn syncback_csv_init<'sync>(
|
|||
meta.properties.shift_remove(&ustr("Contents"));
|
||||
if !meta.is_empty() {
|
||||
dir_syncback.fs_snapshot.add_file(
|
||||
snapshot.path.join("init.meta.json"),
|
||||
snapshot.path.join("init.meta.json5"),
|
||||
crate::json::to_vec_pretty_sorted(&meta)
|
||||
.context("could not serialize new init.meta.json")?,
|
||||
.context("could not serialize new init.meta.json5")?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -256,7 +256,7 @@ fn localization_to_csv(csv_contents: &str) -> anyhow::Result<Vec<u8>> {
|
|||
let mut writer = csv::Writer::from_writer(&mut out);
|
||||
|
||||
let mut csv: Vec<LocalizationEntry> =
|
||||
serde_json::from_str(csv_contents).context("cannot decode JSON from localization table")?;
|
||||
json5::from_str(csv_contents).context("cannot decode JSON from localization table")?;
|
||||
|
||||
// TODO sort this better
|
||||
csv.sort_by(|a, b| a.source.partial_cmp(&b.source).unwrap());
|
||||
|
|
|
|||
|
|
@ -106,9 +106,9 @@ pub fn syncback_dir<'sync>(
|
|||
|
||||
if !meta.is_empty() {
|
||||
dir_syncback.fs_snapshot.add_file(
|
||||
snapshot.path.join("init.meta.json"),
|
||||
snapshot.path.join("init.meta.json5"),
|
||||
crate::json::to_vec_pretty_sorted(&meta)
|
||||
.context("could not serialize new init.meta.json")?,
|
||||
.context("could not serialize new init.meta.json5")?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ pub fn syncback_lua<'sync>(
|
|||
meta_name.to_string()
|
||||
};
|
||||
fs_snapshot.add_file(
|
||||
parent_location.join(format!("{}.meta.json", meta_name)),
|
||||
parent_location.join(format!("{}.meta.json5", meta_name)),
|
||||
crate::json::to_vec_pretty_sorted(&meta).context("cannot serialize metadata")?,
|
||||
);
|
||||
}
|
||||
|
|
@ -225,9 +225,9 @@ pub fn syncback_lua_init<'sync>(
|
|||
|
||||
if !meta.is_empty() {
|
||||
dir_syncback.fs_snapshot.add_file(
|
||||
snapshot.path.join("init.meta.json"),
|
||||
snapshot.path.join("init.meta.json5"),
|
||||
crate::json::to_vec_pretty_sorted(&meta)
|
||||
.context("could not serialize new init.meta.json")?,
|
||||
.context("could not serialize new init.meta.json5")?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ impl AdjacentMetadata {
|
|||
.unwrap_or(file_stem);
|
||||
let meta_path_json = path.with_file_name(format!("{base_name}.meta.json"));
|
||||
let meta_path_jsonc = path.with_file_name(format!("{base_name}.meta.jsonc"));
|
||||
let meta_path_json5 = path.with_file_name(format!("{base_name}.meta.json5"));
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path_json).with_not_found()? {
|
||||
let mut metadata = Self::from_slice(&meta_contents, meta_path_json.clone())?;
|
||||
|
|
@ -72,13 +73,19 @@ impl AdjacentMetadata {
|
|||
}
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path_jsonc).with_not_found()? {
|
||||
let mut metadata = Self::from_slice(&meta_contents, meta_path_json.clone())?;
|
||||
let mut metadata = Self::from_slice(&meta_contents, meta_path_jsonc.clone())?;
|
||||
metadata.apply_all(snapshot)?;
|
||||
}
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path_json5).with_not_found()? {
|
||||
let mut metadata = Self::from_slice(&meta_contents, meta_path_json5.clone())?;
|
||||
metadata.apply_all(snapshot)?;
|
||||
}
|
||||
|
||||
// Rather than pushing these in the snapshot middleware, we can just do it here.
|
||||
snapshot.metadata.relevant_paths.push(meta_path_json);
|
||||
snapshot.metadata.relevant_paths.push(meta_path_jsonc);
|
||||
snapshot.metadata.relevant_paths.push(meta_path_json5);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -290,6 +297,7 @@ impl DirectoryMetadata {
|
|||
) -> anyhow::Result<()> {
|
||||
let meta_path_json = path.join("init.meta.json");
|
||||
let meta_path_jsonc = path.join("init.meta.jsonc");
|
||||
let meta_path_json5 = path.join("init.meta.json5");
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path_json).with_not_found()? {
|
||||
let mut metadata = Self::from_slice(&meta_contents, meta_path_json.clone())?;
|
||||
|
|
@ -301,9 +309,15 @@ impl DirectoryMetadata {
|
|||
metadata.apply_all(snapshot)?;
|
||||
}
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path_json5).with_not_found()? {
|
||||
let mut metadata = Self::from_slice(&meta_contents, meta_path_json5.clone())?;
|
||||
metadata.apply_all(snapshot)?;
|
||||
}
|
||||
|
||||
// Rather than pushing these in the snapshot middleware, we can just do it here.
|
||||
snapshot.metadata.relevant_paths.push(meta_path_json);
|
||||
snapshot.metadata.relevant_paths.push(meta_path_jsonc);
|
||||
snapshot.metadata.relevant_paths.push(meta_path_json5);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -495,32 +509,34 @@ impl DirectoryMetadata {
|
|||
}
|
||||
|
||||
/// Retrieves the meta file that should be applied for the provided directory,
|
||||
/// if it exists.
|
||||
/// if it exists. Checks for .json5, .jsonc, and .json in that order.
|
||||
pub fn dir_meta(vfs: &Vfs, path: &Path) -> anyhow::Result<Option<DirectoryMetadata>> {
|
||||
let meta_path = path.join("init.meta.json");
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
||||
let metadata = DirectoryMetadata::from_slice(&meta_contents, meta_path)?;
|
||||
Ok(Some(metadata))
|
||||
} else {
|
||||
Ok(None)
|
||||
// Check json5 first (preferred), then jsonc, then json
|
||||
for ext in ["init.meta.json5", "init.meta.jsonc", "init.meta.json"] {
|
||||
let meta_path = path.join(ext);
|
||||
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
||||
let metadata = DirectoryMetadata::from_slice(&meta_contents, meta_path)?;
|
||||
return Ok(Some(metadata));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Retrieves the meta file that should be applied for the provided file,
|
||||
/// if it exists.
|
||||
/// if it exists. Checks for .json5, .jsonc, and .json in that order.
|
||||
///
|
||||
/// The `name` field should be the name the metadata should have.
|
||||
pub fn file_meta(vfs: &Vfs, path: &Path, name: &str) -> anyhow::Result<Option<AdjacentMetadata>> {
|
||||
let mut meta_path = path.with_file_name(name);
|
||||
meta_path.set_extension("meta.json");
|
||||
|
||||
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
||||
let metadata = AdjacentMetadata::from_slice(&meta_contents, meta_path)?;
|
||||
Ok(Some(metadata))
|
||||
} else {
|
||||
Ok(None)
|
||||
// Check json5 first (preferred), then jsonc, then json
|
||||
for ext in ["meta.json5", "meta.jsonc", "meta.json"] {
|
||||
let mut meta_path = path.with_file_name(name);
|
||||
meta_path.set_extension(ext);
|
||||
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
||||
let metadata = AdjacentMetadata::from_slice(&meta_contents, meta_path)?;
|
||||
return Ok(Some(metadata));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -430,10 +430,13 @@ pub fn default_sync_rules() -> &'static [SyncRule] {
|
|||
sync_rule!("*.{lua,luau}", ModuleScript),
|
||||
sync_rule!("*.project.json", Project, ".project.json"),
|
||||
sync_rule!("*.project.jsonc", Project, ".project.jsonc"),
|
||||
sync_rule!("*.project.json5", Project, ".project.json5"),
|
||||
sync_rule!("*.model.json", JsonModel, ".model.json"),
|
||||
sync_rule!("*.model.jsonc", JsonModel, ".model.jsonc"),
|
||||
sync_rule!("*.model.json5", JsonModel, ".model.json5"),
|
||||
sync_rule!("*.json", Json, ".json", "*.meta.json"),
|
||||
sync_rule!("*.jsonc", Json, ".jsonc", "*.meta.jsonc"),
|
||||
sync_rule!("*.json5", Json, ".json5", "*.meta.json5"),
|
||||
sync_rule!("*.toml", Toml),
|
||||
sync_rule!("*.csv", Csv),
|
||||
sync_rule!("*.txt", Text),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.csv
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ metadata:
|
|||
- /root/init.csv
|
||||
- /root/init.meta.json
|
||||
- /root/init.meta.jsonc
|
||||
- /root/init.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ metadata:
|
|||
- /root/init.csv
|
||||
- /root/init.meta.json
|
||||
- /root/init.meta.jsonc
|
||||
- /root/init.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.csv
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ metadata:
|
|||
- /foo/init.csv
|
||||
- /foo/init.meta.json
|
||||
- /foo/init.meta.jsonc
|
||||
- /foo/init.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ metadata:
|
|||
- /foo/init.csv
|
||||
- /foo/init.meta.json
|
||||
- /foo/init.meta.jsonc
|
||||
- /foo/init.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
@ -44,6 +45,7 @@ children:
|
|||
- /foo/Child/init.csv
|
||||
- /foo/Child/init.meta.json
|
||||
- /foo/Child/init.meta.jsonc
|
||||
- /foo/Child/init.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.json
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.json
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.client.lua
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.lua
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.lua
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /bar.server.lua
|
||||
- /bar.meta.json
|
||||
- /bar.meta.jsonc
|
||||
- /bar.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.server.lua
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.server.lua
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ metadata:
|
|||
- /root/init.csv
|
||||
- /root/init.meta.json
|
||||
- /root/init.meta.jsonc
|
||||
- /root/init.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ metadata:
|
|||
- /root/init.csv
|
||||
- /root/init.meta.json
|
||||
- /root/init.meta.jsonc
|
||||
- /root/init.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.plugin.lua
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: false
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.client.lua
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: false
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.lua
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: false
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.lua
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: false
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /bar.server.lua
|
||||
- /bar.meta.json
|
||||
- /bar.meta.jsonc
|
||||
- /bar.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: false
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.server.lua
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: false
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.server.lua
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: false
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ metadata:
|
|||
relevant_paths:
|
||||
- /foo/bar.meta.json
|
||||
- /foo/bar.meta.jsonc
|
||||
- /foo/bar.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ metadata:
|
|||
relevant_paths:
|
||||
- /foo/bar.meta.json
|
||||
- /foo/bar.meta.jsonc
|
||||
- /foo/bar.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ metadata:
|
|||
relevant_paths:
|
||||
- /foo/init.meta.json
|
||||
- /foo/init.meta.jsonc
|
||||
- /foo/init.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ metadata:
|
|||
relevant_paths:
|
||||
- /foo/init.meta.json
|
||||
- /foo/init.meta.jsonc
|
||||
- /foo/init.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo/other.txt
|
||||
- /foo/other.meta.json
|
||||
- /foo/other.meta.jsonc
|
||||
- /foo/other.meta.json5
|
||||
- /foo/default.project.json
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.toml
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- foo.toml
|
||||
- foo.meta.json
|
||||
- foo.meta.jsonc
|
||||
- foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.txt
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.txt
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- /foo.yaml
|
||||
- /foo.meta.json
|
||||
- /foo.meta.jsonc
|
||||
- /foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ metadata:
|
|||
- foo.yaml
|
||||
- foo.meta.json
|
||||
- foo.meta.jsonc
|
||||
- foo.meta.json5
|
||||
context:
|
||||
emit_legacy_scripts: true
|
||||
decode_windows_invalid_chars: false
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ pub fn syncback_txt<'sync>(
|
|||
new_inst.name.clone()
|
||||
};
|
||||
fs_snapshot.add_file(
|
||||
parent.join(format!("{}.meta.json", meta_name)),
|
||||
parent.join(format!("{}.meta.json5", meta_name)),
|
||||
crate::json::to_vec_pretty_sorted(&meta).context("could not serialize metadata")?,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,13 +85,13 @@ pub fn name_for_inst<'old>(
|
|||
pub fn extension_for_middleware(middleware: Middleware) -> &'static str {
|
||||
match middleware {
|
||||
Middleware::Csv => "csv",
|
||||
Middleware::JsonModel => "model.json",
|
||||
Middleware::Json => "json",
|
||||
Middleware::JsonModel => "model.json5",
|
||||
Middleware::Json => "json5",
|
||||
Middleware::ServerScript => "server.luau",
|
||||
Middleware::ClientScript => "client.luau",
|
||||
Middleware::ModuleScript => "luau",
|
||||
Middleware::PluginScript => "plugin.luau",
|
||||
Middleware::Project => "project.json",
|
||||
Middleware::Project => "project.json5",
|
||||
Middleware::Rbxm => "rbxm",
|
||||
Middleware::Rbxmx => "rbxmx",
|
||||
Middleware::Toml => "toml",
|
||||
|
|
|
|||
|
|
@ -163,20 +163,14 @@ impl TestServeSession {
|
|||
let url = format!("http://localhost:{}/api/rojo", self.port);
|
||||
let body = reqwest::blocking::get(url)?.text()?;
|
||||
|
||||
let value = jsonc_parser::parse_to_serde_value(&body, &Default::default())
|
||||
.expect("Failed to parse JSON")
|
||||
.expect("No JSON value");
|
||||
Ok(serde_json::from_value(value).expect("Server returned malformed response"))
|
||||
Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
|
||||
}
|
||||
|
||||
pub fn get_api_read(&self, id: Ref) -> Result<ReadResponse<'_>, reqwest::Error> {
|
||||
let url = format!("http://localhost:{}/api/read/{}", self.port, id);
|
||||
let body = reqwest::blocking::get(url)?.text()?;
|
||||
|
||||
let value = jsonc_parser::parse_to_serde_value(&body, &Default::default())
|
||||
.expect("Failed to parse JSON")
|
||||
.expect("No JSON value");
|
||||
Ok(serde_json::from_value(value).expect("Server returned malformed response"))
|
||||
Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
|
||||
}
|
||||
|
||||
pub fn get_api_socket_packet(
|
||||
|
|
|
|||
|
|
@ -35,21 +35,21 @@ syncback_tests! {
|
|||
// Ensures that syncback works with CSVs
|
||||
csv => ["src/csv_init/init.csv", "src/csv.csv"],
|
||||
// Ensures that if a RojoId is duplicated somewhere in the project, it's
|
||||
// rewritten rather than synced back as a conflict
|
||||
// rewritten rather than synced back as a conflict (preserves existing .json)
|
||||
duplicate_rojo_id => ["container.model.json"],
|
||||
// Ensures that the `ignorePaths` setting works for additions
|
||||
ignore_paths_adding => ["src/int_value.model.json", "src/subfolder/string_value.txt"],
|
||||
// Ensures that the `ignorePaths` setting works for additions (new files get .json5)
|
||||
ignore_paths_adding => ["src/int_value.model.json5", "src/subfolder/string_value.txt"],
|
||||
// Ensures that the `ignorePaths` setting works for `init` files
|
||||
ignore_paths_init => ["src/non-init.luau", "src/init-file/init.luau"],
|
||||
// Ensures that the `ignorePaths` setting works for removals
|
||||
ignore_paths_removing => ["src/Message.model.json"],
|
||||
// Ensures that the `ignorePaths` setting works for removals (new file gets .json5)
|
||||
ignore_paths_removing => ["src/Message.model.json5"],
|
||||
// Ensures that `ignoreTrees` works for additions
|
||||
ignore_trees_adding => [],
|
||||
// Ensures that `ignoreTrees` works for removals
|
||||
ignore_trees_removing => [],
|
||||
// Ensures that all of the JSON middlewares are handled as expected
|
||||
// Ensures that all of the JSON middlewares are handled as expected (preserves existing .json)
|
||||
json_middlewares => ["src/dir_with_meta/init.meta.json", "src/model_json.model.json", "src/project_json.project.json"],
|
||||
// Ensures projects that refer to other projects work as expected
|
||||
// Ensures projects that refer to other projects work as expected (preserves existing .json)
|
||||
nested_projects => ["nested.project.json", "string_value.txt"],
|
||||
// Ensures files that are ignored by nested projects are picked up if
|
||||
// they're included in second project. Unusual but perfectly workable
|
||||
|
|
@ -58,25 +58,25 @@ syncback_tests! {
|
|||
// Ensures that projects respect `init` files when they're directly referenced from a node
|
||||
project_init => ["src/init.luau"],
|
||||
// Ensures that projects can be reserialized by syncback and that
|
||||
// default.project.json doesn't change unexpectedly.
|
||||
// default.project.json doesn't change unexpectedly (preserves existing .json)
|
||||
project_reserialize => ["attribute_mismatch.luau", "property_mismatch.project.json"],
|
||||
// Confirms that Instances that cannot serialize as directories serialize as rbxms
|
||||
rbxm_fallback => [],
|
||||
// Ensures that ref properties are linked properly on the file system
|
||||
// Ensures that ref properties are linked properly on the file system (preserves existing .json)
|
||||
ref_properties => ["src/pointer.model.json", "src/target.model.json"],
|
||||
// Ensures that ref properties are linked when no attributes are manually
|
||||
// set in the DataModel
|
||||
ref_properties_blank => ["src/pointer.model.json", "src/target.txt"],
|
||||
// Ensures that if there is a conflict in RojoRefs, one of them is rewritten.
|
||||
// set in the DataModel (new files get .json5)
|
||||
ref_properties_blank => ["src/pointer.model.json5", "src/target.txt"],
|
||||
// Ensures that if there is a conflict in RojoRefs, one of them is rewritten (preserves existing .json)
|
||||
ref_properties_conflict => ["src/Pointer_2.model.json", "src/Target_2.model.json"],
|
||||
// Ensures that having multiple pointers that are aimed at the same target doesn't trigger ref rewrites.
|
||||
ref_properties_duplicate => [],
|
||||
// Ensures that ref properties that point to nothing after the prune both
|
||||
// do not leave any trace of themselves
|
||||
ref_properties_pruned => ["src/Pointer1.model.json", "src/Pointer2.model.json", "src/Pointer3.model.json"],
|
||||
// do not leave any trace of themselves (new files get .json5)
|
||||
ref_properties_pruned => ["src/Pointer1.model.json5", "src/Pointer2.model.json5", "src/Pointer3.model.json5"],
|
||||
// Ensures that the old middleware is respected during syncback
|
||||
respect_old_middleware => ["default.project.json", "src/model_json.model.json", "src/rbxm.rbxm", "src/rbxmx.rbxmx"],
|
||||
// Ensures that the `$schema` field roundtrips with syncback
|
||||
// Ensures that the `$schema` field roundtrips with syncback (preserves existing .json)
|
||||
schema_roundtrip => ["default.project.json", "src/model.model.json", "src/init/init.meta.json", "src/adjacent.meta.json"],
|
||||
// Ensures that StringValues inside project files are written to the
|
||||
// project file, but only if they don't have `$path` set
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue