Merge branch 'master' of https://github.com/boatbomber/rojo into headless-api

This commit is contained in:
boatbomber 2025-10-28 19:36:20 -07:00
commit e102c9d5cf
18 changed files with 1099 additions and 409 deletions

View file

@ -60,7 +60,7 @@ jobs:
submodules: true
- name: Install Rust
uses: dtolnay/rust-toolchain@1.83.0
uses: dtolnay/rust-toolchain@1.88.0
- name: Restore Rust Cache
uses: actions/cache/restore@v4

File diff suppressed because it is too large Load diff

92
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@ -243,7 +243,7 @@ checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"syn 1.0.109",
]
@ -665,9 +665,9 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"syn 2.0.52",
"syn 2.0.108",
]
[[package]]
@ -1033,6 +1033,15 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "jsonc-parser"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ec4ac49f13c7b00f435f8a5bb55d725705e2cf620df35a5859321595102eb7e"
dependencies = [
"serde_json",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
@ -1393,9 +1402,9 @@ checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80"
dependencies = [
"pest",
"pest_meta",
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"syn 2.0.52",
"syn 2.0.108",
]
[[package]]
@ -1478,7 +1487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"syn 1.0.109",
"version_check",
@ -1490,7 +1499,7 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"version_check",
]
@ -1518,9 +1527,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.78"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
@ -1542,7 +1551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [
"quote 1.0.35",
"syn 2.0.52",
"syn 2.0.108",
]
[[package]]
@ -1560,7 +1569,7 @@ version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
]
[[package]]
@ -1903,6 +1912,7 @@ dependencies = [
"hyper",
"insta",
"jod-thread",
"jsonc-parser",
"log",
"maplit",
"memofs",
@ -2054,10 +2064,11 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
[[package]]
name = "serde"
version = "1.0.197"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
@ -2072,25 +2083,36 @@ dependencies = [
]
[[package]]
name = "serde_derive"
version = "1.0.197"
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"proc-macro2 1.0.78",
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.35",
"syn 2.0.52",
"syn 2.0.108",
]
[[package]]
name = "serde_json"
version = "1.0.114"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
]
[[package]]
@ -2195,18 +2217,18 @@ version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.52"
version = "2.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
dependencies = [
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"unicode-ident",
]
@ -2289,9 +2311,9 @@ version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"syn 2.0.52",
"syn 2.0.108",
]
[[package]]
@ -2401,9 +2423,9 @@ version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"syn 2.0.52",
"syn 2.0.108",
]
[[package]]
@ -2638,9 +2660,9 @@ dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"syn 2.0.52",
"syn 2.0.108",
"wasm-bindgen-shared",
]
@ -2672,9 +2694,9 @@ version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"syn 2.0.52",
"syn 2.0.108",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -2970,9 +2992,9 @@ version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2 1.0.78",
"proc-macro2 1.0.103",
"quote 1.0.35",
"syn 2.0.52",
"syn 2.0.108",
]
[[package]]

View file

@ -1,7 +1,7 @@
[package]
name = "rojo"
version = "7.6.0"
rust-version = "1.83"
rust-version = "1.88"
authors = [
"Lucien Greathouse <me@lpghatguy.com>",
"Micah Reid <git@dekkonot.com>",
@ -85,7 +85,8 @@ reqwest = { version = "0.11.24", default-features = false, features = [
ritz = "0.1.0"
roblox_install = "1.0.0"
serde = { version = "1.0.197", features = ["derive", "rc"] }
serde_json = "1.0.114"
serde_json = "1.0.145"
jsonc-parser = { version = "0.27.0", features = ["serde"] }
toml = "0.5.11"
termcolor = "1.4.1"
thiserror = "1.0.57"

View file

@ -40,7 +40,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
Pull requests are welcome!
Rojo supports Rust 1.83 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
Rojo supports Rust 1.88 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
## License
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.

View file

@ -1,3 +1,5 @@
# Roblox Studio lock files
/*.rbxlx.lock
/*.rbxl.lock
/*.rbxl.lock
sourcemap.json

View file

@ -3,4 +3,6 @@
# Roblox Studio lock files
/*.rbxlx.lock
/*.rbxl.lock
/*.rbxl.lock
sourcemap.json

View file

@ -1,3 +1,5 @@
# Plugin model files
/{project_name}.rbxmx
/{project_name}.rbxm
sourcemap.json

View file

@ -43,8 +43,8 @@ impl Serialize for Glob {
impl<'de> Deserialize<'de> for Glob {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let glob = <&str as Deserialize>::deserialize(deserializer)?;
let glob = String::deserialize(deserializer)?;
Glob::new(glob).map_err(D::Error::custom)
Glob::new(&glob).map_err(D::Error::custom)
}
}

313
src/json.rs Normal file
View file

@ -0,0 +1,313 @@
//! Utilities for parsing JSON with comments (JSONC) and deserializing to Rust types.
//!
//! This module provides convenient wrappers around `jsonc_parser` and `serde_json`
//! to reduce boilerplate and improve ergonomics when working with JSONC files.
use anyhow::Context as _;
use serde::de::DeserializeOwned;
/// Parse JSONC text into a `serde_json::Value`.
///
/// This handles the common pattern of calling `jsonc_parser::parse_to_serde_value`
/// and unwrapping the `Option` with a clear error message.
///
/// # Errors
///
/// Returns an error if:
/// - The text is not valid JSONC
/// - The text contains no JSON value
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"))
}
/// Parse JSONC text into a `serde_json::Value` with a custom context message.
///
/// This is useful when you want to provide a specific error message that includes
/// additional information like the file path.
///
/// # Errors
///
/// Returns an error if:
/// - The text is not valid JSONC
/// - The text contains no JSON value
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()))
}
/// Parse JSONC text and deserialize it into a specific type.
///
/// This combines parsing JSONC and deserializing into a single operation,
/// eliminating the need to manually chain `parse_to_serde_value` and `from_value`.
///
/// # Errors
///
/// Returns an error if:
/// - The text is not valid JSONC
/// - 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")
}
/// Parse JSONC text and deserialize it into a specific type with a custom context message.
///
/// This is useful when you want to provide a specific error message that includes
/// additional information like the file path.
///
/// # Errors
///
/// Returns an error if:
/// - The text is not valid JSONC
/// - The text contains no JSON value
/// - The value cannot be deserialized into type `T`
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()))
}
/// Parse JSONC bytes into a `serde_json::Value` with a custom context message.
///
/// This handles UTF-8 conversion and JSONC parsing in one step.
///
/// # Errors
///
/// Returns an error if:
/// - The bytes are not valid UTF-8
/// - The text is not valid JSONC
/// - The text contains no JSON value
pub fn parse_value_from_slice_with_context(
slice: &[u8],
context: impl Fn() -> String,
) -> anyhow::Result<serde_json::Value> {
let text = std::str::from_utf8(slice)
.with_context(|| format!("{}: File is not valid UTF-8", context()))?;
parse_value_with_context(text, context)
}
/// Parse JSONC bytes and deserialize it into a specific type.
///
/// This handles UTF-8 conversion, JSONC parsing, and deserialization in one step.
///
/// # Errors
///
/// Returns an error if:
/// - The bytes are not valid UTF-8
/// - The text is not valid JSONC
/// - The text contains no JSON value
/// - The value cannot be deserialized into type `T`
pub fn from_slice<T: DeserializeOwned>(slice: &[u8]) -> anyhow::Result<T> {
let text = std::str::from_utf8(slice).context("File is not valid UTF-8")?;
from_str(text)
}
/// Parse JSONC bytes and deserialize it into a specific type with a custom context message.
///
/// This handles UTF-8 conversion, JSONC parsing, and deserialization in one step.
///
/// # Errors
///
/// Returns an error if:
/// - The bytes are not valid UTF-8
/// - The text is not valid JSONC
/// - The text contains no JSON value
/// - The value cannot be deserialized into type `T`
pub fn from_slice_with_context<T: DeserializeOwned>(
slice: &[u8],
context: impl Fn() -> String,
) -> anyhow::Result<T> {
let text = std::str::from_utf8(slice)
.with_context(|| format!("{}: File is not valid UTF-8", context()))?;
from_str_with_context(text, context)
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[test]
fn test_parse_value() {
let value = parse_value(r#"{"foo": "bar"}"#).unwrap();
assert_eq!(value["foo"], "bar");
}
#[test]
fn test_parse_value_with_comments() {
let value = parse_value(
r#"{
// This is a comment
"foo": "bar" // Inline comment
}"#,
)
.unwrap();
assert_eq!(value["foo"], "bar");
}
#[test]
fn test_parse_value_with_trailing_comma() {
let value = parse_value(
r#"{
"foo": "bar",
"baz": 123,
}"#,
)
.unwrap();
assert_eq!(value["foo"], "bar");
assert_eq!(value["baz"], 123);
}
#[test]
fn test_parse_value_empty() {
let err = parse_value("").unwrap_err();
assert!(err.to_string().contains("no JSON value"));
}
#[test]
fn test_parse_value_invalid() {
let err = parse_value("{invalid}").unwrap_err();
assert!(err.to_string().contains("parse"));
}
#[test]
fn test_parse_value_with_context() {
let err = parse_value_with_context("{invalid}", || "test.json".to_string()).unwrap_err();
assert!(err.to_string().contains("test.json"));
assert!(err.to_string().contains("parse"));
}
#[derive(Debug, Deserialize, PartialEq)]
struct TestStruct {
foo: String,
bar: i32,
}
#[test]
fn test_from_str() {
let result: TestStruct = from_str(r#"{"foo": "hello", "bar": 42}"#).unwrap();
assert_eq!(
result,
TestStruct {
foo: "hello".to_string(),
bar: 42
}
);
}
#[test]
fn test_from_str_with_comments() {
let result: TestStruct = from_str(
r#"{
// Comment
"foo": "hello",
"bar": 42, // Trailing comma is fine
}"#,
)
.unwrap();
assert_eq!(
result,
TestStruct {
foo: "hello".to_string(),
bar: 42
}
);
}
#[test]
fn test_from_str_invalid_type() {
let err = from_str::<TestStruct>(r#"{"foo": "hello"}"#).unwrap_err();
assert!(err.to_string().contains("deserialize"));
}
#[test]
fn test_from_str_with_context() {
let err = from_str_with_context::<TestStruct>(r#"{"foo": "hello"}"#, || {
"config.json".to_string()
})
.unwrap_err();
assert!(err.to_string().contains("config.json"));
assert!(err.to_string().contains("Invalid JSON structure"));
}
#[test]
fn test_parse_value_from_slice_with_context() {
let err = parse_value_from_slice_with_context(b"{invalid}", || "test.json".to_string())
.unwrap_err();
assert!(err.to_string().contains("test.json"));
assert!(err.to_string().contains("parse"));
}
#[test]
fn test_parse_value_from_slice_with_context_invalid_utf8() {
let err = parse_value_from_slice_with_context(&[0xFF, 0xFF], || "test.json".to_string())
.unwrap_err();
assert!(err.to_string().contains("test.json"));
assert!(err.to_string().contains("UTF-8"));
}
#[test]
fn test_from_slice() {
let result: TestStruct = from_slice(br#"{"foo": "hello", "bar": 42}"#).unwrap();
assert_eq!(
result,
TestStruct {
foo: "hello".to_string(),
bar: 42
}
);
}
#[test]
fn test_from_slice_with_comments() {
let result: TestStruct = from_slice(
br#"{
// Comment
"foo": "hello",
"bar": 42, // Trailing comma is fine
}"#,
)
.unwrap();
assert_eq!(
result,
TestStruct {
foo: "hello".to_string(),
bar: 42
}
);
}
#[test]
fn test_from_slice_invalid_utf8() {
let err = from_slice::<TestStruct>(&[0xFF, 0xFF]).unwrap_err();
assert!(err.to_string().contains("UTF-8"));
}
#[test]
fn test_from_slice_with_context() {
let err = from_slice_with_context::<TestStruct>(br#"{"foo": "hello"}"#, || {
"config.json".to_string()
})
.unwrap_err();
assert!(err.to_string().contains("config.json"));
assert!(err.to_string().contains("Invalid JSON structure"));
}
#[test]
fn test_from_slice_with_context_invalid_utf8() {
let err =
from_slice_with_context::<TestStruct>(&[0xFF, 0xFF], || "config.json".to_string())
.unwrap_err();
assert!(err.to_string().contains("config.json"));
assert!(err.to_string().contains("UTF-8"));
}
}

View file

@ -10,6 +10,7 @@ mod tree_view;
mod auth_cookie;
mod change_processor;
mod glob;
mod json;
mod lua_ast;
mod message_queue;
mod multimap;

View file

@ -11,7 +11,7 @@ use rbx_dom_weak::{Ustr, UstrMap};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{glob::Glob, resolution::UnresolvedValue, snapshot::SyncRule};
use crate::{glob::Glob, json, resolution::UnresolvedValue, snapshot::SyncRule};
static PROJECT_FILENAME: &str = "default.project.json";
@ -214,8 +214,11 @@ impl Project {
project_file_location: PathBuf,
fallback_name: Option<&str>,
) -> Result<Self, Error> {
let mut project: Self = serde_json::from_slice(contents).map_err(|source| Error::Json {
source,
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(),
)),
path: project_file_location.clone(),
})?;
project.file_location = project_file_location;
@ -399,13 +402,13 @@ mod test {
#[test]
fn path_node_required() {
let path_node: PathNode = serde_json::from_str(r#""src""#).unwrap();
let path_node: PathNode = json::from_str(r#""src""#).unwrap();
assert_eq!(path_node, PathNode::Required(PathBuf::from("src")));
}
#[test]
fn path_node_optional() {
let path_node: PathNode = serde_json::from_str(r#"{ "optional": "src" }"#).unwrap();
let path_node: PathNode = json::from_str(r#"{ "optional": "src" }"#).unwrap();
assert_eq!(
path_node,
PathNode::Optional(OptionalPathNode::new(PathBuf::from("src")))
@ -414,7 +417,7 @@ mod test {
#[test]
fn project_node_required() {
let project_node: ProjectNode = serde_json::from_str(
let project_node: ProjectNode = json::from_str(
r#"{
"$path": "src"
}"#,
@ -429,7 +432,7 @@ mod test {
#[test]
fn project_node_optional() {
let project_node: ProjectNode = serde_json::from_str(
let project_node: ProjectNode = json::from_str(
r#"{
"$path": { "optional": "src" }
}"#,
@ -446,7 +449,7 @@ mod test {
#[test]
fn project_node_none() {
let project_node: ProjectNode = serde_json::from_str(
let project_node: ProjectNode = json::from_str(
r#"{
"$className": "Folder"
}"#,
@ -458,7 +461,7 @@ mod test {
#[test]
fn project_node_optional_serialize_absolute() {
let project_node: ProjectNode = serde_json::from_str(
let project_node: ProjectNode = json::from_str(
r#"{
"$path": { "optional": "..\\src" }
}"#,
@ -471,7 +474,7 @@ mod test {
#[test]
fn project_node_optional_serialize_absolute_no_change() {
let project_node: ProjectNode = serde_json::from_str(
let project_node: ProjectNode = json::from_str(
r#"{
"$path": { "optional": "../src" }
}"#,
@ -484,7 +487,7 @@ mod test {
#[test]
fn project_node_optional_serialize_optional() {
let project_node: ProjectNode = serde_json::from_str(
let project_node: ProjectNode = json::from_str(
r#"{
"$path": "..\\src"
}"#,
@ -494,4 +497,57 @@ mod test {
let serialized = serde_json::to_string(&project_node).unwrap();
assert_eq!(serialized, r#"{"$path":"../src"}"#);
}
#[test]
fn project_with_jsonc_features() {
// Test that JSONC features (comments and trailing commas) are properly handled
let project_json = r#"{
// This is a single-line comment
"name": "TestProject",
/* This is a
multi-line comment */
"tree": {
"$path": "src", // Comment after value
},
"servePort": 34567,
"emitLegacyScripts": false,
// Test glob parsing with comments
"globIgnorePaths": [
"**/*.spec.lua", // Ignore test files
"**/*.test.lua",
],
"syncRules": [
{
"pattern": "*.data.json",
"use": "json", // Trailing comma in object
},
{
"pattern": "*.module.lua",
"use": "moduleScript",
}, // Trailing comma in array
], // Another trailing comma
}"#;
let project = Project::load_from_slice(
project_json.as_bytes(),
PathBuf::from("/test/default.project.json"),
None,
)
.expect("Failed to parse project with JSONC features");
// Verify the parsed values
assert_eq!(project.name, Some("TestProject".to_string()));
assert_eq!(project.serve_port, Some(34567));
assert_eq!(project.emit_legacy_scripts, Some(false));
// Verify glob_ignore_paths were parsed correctly
assert_eq!(project.glob_ignore_paths.len(), 2);
assert!(project.glob_ignore_paths[0].is_match("test/foo.spec.lua"));
assert!(project.glob_ignore_paths[1].is_match("test/bar.test.lua"));
// Verify sync_rules were parsed correctly
assert_eq!(project.sync_rules.len(), 2);
assert!(project.sync_rules[0].include.is_match("data.data.json"));
assert!(project.sync_rules[1].include.is_match("init.module.lua"));
}
}

View file

@ -248,14 +248,15 @@ fn nonexhaustive_list(values: &[&str]) -> String {
#[cfg(test)]
mod test {
use super::*;
use crate::json;
fn resolve(class: &str, prop: &str, json_value: &str) -> Variant {
let unresolved: UnresolvedValue = serde_json::from_str(json_value).unwrap();
let unresolved: UnresolvedValue = json::from_str(json_value).unwrap();
unresolved.resolve(class, prop).unwrap()
}
fn resolve_unambiguous(json_value: &str) -> Variant {
let unresolved: UnresolvedValue = serde_json::from_str(json_value).unwrap();
let unresolved: UnresolvedValue = json::from_str(json_value).unwrap();
unresolved.resolve_unambiguous().unwrap()
}

View file

@ -1,10 +1,10 @@
use std::path::Path;
use anyhow::Context;
use memofs::{IoResultExt, Vfs};
use rbx_dom_weak::ustr;
use crate::{
json,
lua_ast::{Expression, Statement},
snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot},
};
@ -19,8 +19,9 @@ pub fn snapshot_json(
) -> anyhow::Result<Option<InstanceSnapshot>> {
let contents = vfs.read(path)?;
let value: serde_json::Value = serde_json::from_slice(&contents)
.with_context(|| format!("File contains malformed JSON: {}", path.display()))?;
let value = json::parse_value_from_slice_with_context(&contents, || {
format!("File contains malformed JSON: {}", path.display())
})?;
let as_lua = json_to_lua(value).to_string();

View file

@ -9,6 +9,7 @@ use rbx_dom_weak::{
use serde::Deserialize;
use crate::{
json,
resolution::UnresolvedValue,
snapshot::{InstanceContext, InstanceSnapshot},
RojoRef,
@ -28,8 +29,9 @@ pub fn snapshot_json_model(
return Ok(None);
}
let mut instance: JsonModel = serde_json::from_str(contents_str)
.with_context(|| format!("File is not a valid JSON model: {}", path.display()))?;
let mut instance: JsonModel = json::from_str_with_context(contents_str, || {
format!("File is not a valid JSON model: {}", path.display())
})?;
if let Some(top_level_name) = &instance.name {
let new_name = format!("{}.model.json", top_level_name);

View file

@ -4,7 +4,7 @@ use anyhow::{format_err, Context};
use rbx_dom_weak::{types::Attributes, Ustr, UstrMap};
use serde::{Deserialize, Serialize};
use crate::{resolution::UnresolvedValue, snapshot::InstanceSnapshot, RojoRef};
use crate::{json, resolution::UnresolvedValue, snapshot::InstanceSnapshot, RojoRef};
/// Represents metadata in a sibling file with the same basename.
///
@ -34,7 +34,7 @@ pub struct AdjacentMetadata {
impl AdjacentMetadata {
pub fn from_slice(slice: &[u8], path: PathBuf) -> anyhow::Result<Self> {
let mut meta: Self = serde_json::from_slice(slice).with_context(|| {
let mut meta: Self = json::from_slice_with_context(slice, || {
format!(
"File contained malformed .meta.json data: {}",
path.display()
@ -131,7 +131,7 @@ pub struct DirectoryMetadata {
impl DirectoryMetadata {
pub fn from_slice(slice: &[u8], path: PathBuf) -> anyhow::Result<Self> {
let mut meta: Self = serde_json::from_slice(slice).with_context(|| {
let mut meta: Self = json::from_slice_with_context(slice, || {
format!(
"File contained malformed init.meta.json data: {}",
path.display()

View file

@ -17,6 +17,7 @@ use rbx_dom_weak::{
};
use crate::{
json,
serve_session::ServeSession,
snapshot::{InstanceWithMeta, PatchSet, PatchUpdate},
web::{
@ -139,7 +140,7 @@ impl ApiService {
let body = body::to_bytes(request.into_body()).await.unwrap();
let request: WriteRequest = match serde_json::from_slice(&body) {
let request: WriteRequest = match json::from_slice(&body) {
Ok(request) => request,
Err(err) => {
return json(

View file

@ -157,14 +157,20 @@ impl TestServeSession {
let url = format!("http://localhost:{}/api/rojo", self.port);
let body = reqwest::blocking::get(url)?.text()?;
Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
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"))
}
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()?;
Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
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"))
}
pub fn get_api_subscribe(