forked from rojo-rbx/rojo
Compare commits
4 Commits
v7.7.0-rc.
...
feature/da
| Author | SHA1 | Date | |
|---|---|---|---|
|
c552fdc52e
|
|||
|
|
02b41133f8 | ||
|
|
d08780fc14 | ||
|
|
b89cc7f398 |
@@ -30,6 +30,11 @@ Making a new release? Simply add the new header with the version and date undern
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
* Fixed a bug caused by having reference properties (such as `ObjectValue.Value`) that point to an Instance not included in syncback. ([#1179])
|
||||||
|
* Fixed instance replacement fallback failing when too many instances needed to be replaced. ([#1192])
|
||||||
|
|
||||||
|
[#1179]: https://github.com/rojo-rbx/rojo/pull/1179
|
||||||
|
[#1192]: https://github.com/rojo-rbx/rojo/pull/1192
|
||||||
|
|
||||||
## [7.7.0-rc.1] (November 27th, 2025)
|
## [7.7.0-rc.1] (November 27th, 2025)
|
||||||
|
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1313,7 +1313,7 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memofs"
|
name = "memofs"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"fs-err",
|
"fs-err",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ name = "build"
|
|||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memofs = { version = "0.3.0", path = "crates/memofs" }
|
memofs = { version = "0.3.1", path = "crates/memofs" }
|
||||||
|
|
||||||
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
||||||
# rbx_binary = { path = "../rbx-dom/rbx_binary", features = [
|
# rbx_binary = { path = "../rbx-dom/rbx_binary", features = [
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# memofs Changelog
|
# memofs Changelog
|
||||||
|
|
||||||
## Unreleased Changes
|
## Unreleased Changes
|
||||||
|
|
||||||
|
## 0.3.1 (2025-11-27)
|
||||||
* Added `Vfs::exists`. [#1169]
|
* Added `Vfs::exists`. [#1169]
|
||||||
* Added `create_dir` and `create_dir_all` to allow creating directories. [#937]
|
* Added `create_dir` and `create_dir_all` to allow creating directories. [#937]
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "memofs"
|
name = "memofs"
|
||||||
description = "Virtual filesystem with configurable backends."
|
description = "Virtual filesystem with configurable backends."
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
authors = [
|
authors = [
|
||||||
"Lucien Greathouse <me@lpghatguy.com>",
|
"Lucien Greathouse <me@lpghatguy.com>",
|
||||||
"Micah Reid <git@dekkonot.com>",
|
"Micah Reid <git@dekkonot.com>",
|
||||||
|
|||||||
@@ -31,17 +31,11 @@ impl VfsBackend for NoopBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_dir(&mut self, _path: &Path) -> io::Result<()> {
|
fn create_dir(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
fn create_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_file(&mut self, _path: &Path) -> io::Result<()> {
|
fn remove_file(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
|
|||||||
@@ -290,31 +290,39 @@ function ApiContext:open(id)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ApiContext:serialize(ids: { string })
|
function ApiContext:serialize(ids: { string })
|
||||||
local url = ("%s/api/serialize/%s"):format(self.__baseUrl, table.concat(ids, ","))
|
local url = ("%s/api/serialize"):format(self.__baseUrl)
|
||||||
|
local request_body = Http.jsonEncode({ sessionId = self.__sessionId, ids = ids })
|
||||||
|
|
||||||
return Http.get(url):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
return Http.post(url, request_body)
|
||||||
if body.sessionId ~= self.__sessionId then
|
:andThen(rejectFailedRequests)
|
||||||
return Promise.reject("Server changed ID")
|
:andThen(Http.Response.json)
|
||||||
end
|
:andThen(function(response_body)
|
||||||
|
if response_body.sessionId ~= self.__sessionId then
|
||||||
|
return Promise.reject("Server changed ID")
|
||||||
|
end
|
||||||
|
|
||||||
assert(validateApiSerialize(body))
|
assert(validateApiSerialize(response_body))
|
||||||
|
|
||||||
return body
|
return response_body
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ApiContext:refPatch(ids: { string })
|
function ApiContext:refPatch(ids: { string })
|
||||||
local url = ("%s/api/ref-patch/%s"):format(self.__baseUrl, table.concat(ids, ","))
|
local url = ("%s/api/ref-patch"):format(self.__baseUrl)
|
||||||
|
local request_body = Http.jsonEncode({ sessionId = self.__sessionId, ids = ids })
|
||||||
|
|
||||||
return Http.get(url):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
return Http.post(url, request_body)
|
||||||
if body.sessionId ~= self.__sessionId then
|
:andThen(rejectFailedRequests)
|
||||||
return Promise.reject("Server changed ID")
|
:andThen(Http.Response.json)
|
||||||
end
|
:andThen(function(response_body)
|
||||||
|
if response_body.sessionId ~= self.__sessionId then
|
||||||
|
return Promise.reject("Server changed ID")
|
||||||
|
end
|
||||||
|
|
||||||
assert(validateApiRefPatch(body))
|
assert(validateApiRefPatch(response_body))
|
||||||
|
|
||||||
return body
|
return response_body
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return ApiContext
|
return ApiContext
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/syncback.rs
|
||||||
|
expression: src/Pointer1.model.json
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"className": "ObjectValue"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/syncback.rs
|
||||||
|
expression: src/Pointer2.model.json
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"className": "ObjectValue"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/syncback.rs
|
||||||
|
expression: src/Pointer3.model.json
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"className": "ObjectValue"
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "ref_properties_pruned",
|
||||||
|
"tree": {
|
||||||
|
"$className": "DataModel",
|
||||||
|
"ServerScriptService": {
|
||||||
|
"$path": "src"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
rojo-test/syncback-tests/ref_properties_pruned/input.rbxl
Normal file
BIN
rojo-test/syncback-tests/ref_properties_pruned/input.rbxl
Normal file
Binary file not shown.
@@ -54,6 +54,11 @@ pub struct SyncbackCommand {
|
|||||||
/// If provided, the prompt for writing to the file system is skipped.
|
/// If provided, the prompt for writing to the file system is skipped.
|
||||||
#[clap(long, short = 'y')]
|
#[clap(long, short = 'y')]
|
||||||
pub non_interactive: bool,
|
pub non_interactive: bool,
|
||||||
|
|
||||||
|
/// If provided, forces syncback to use JSON model files instead of binary
|
||||||
|
/// .rbxm files for instances that would otherwise serialize as binary.
|
||||||
|
#[clap(long)]
|
||||||
|
pub dangerously_force_json: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyncbackCommand {
|
impl SyncbackCommand {
|
||||||
@@ -104,6 +109,7 @@ impl SyncbackCommand {
|
|||||||
&mut dom_old,
|
&mut dom_old,
|
||||||
dom_new,
|
dom_new,
|
||||||
session_old.root_project(),
|
session_old.root_project(),
|
||||||
|
self.dangerously_force_json,
|
||||||
)?;
|
)?;
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Syncback finished in {:.02}s!",
|
"Syncback finished in {:.02}s!",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ pub fn syncback_loop(
|
|||||||
old_tree: &mut RojoTree,
|
old_tree: &mut RojoTree,
|
||||||
mut new_tree: WeakDom,
|
mut new_tree: WeakDom,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
|
force_json: bool,
|
||||||
) -> anyhow::Result<FsSnapshot> {
|
) -> anyhow::Result<FsSnapshot> {
|
||||||
let ignore_patterns = project
|
let ignore_patterns = project
|
||||||
.syncback_rules
|
.syncback_rules
|
||||||
@@ -153,6 +154,7 @@ pub fn syncback_loop(
|
|||||||
old_tree,
|
old_tree,
|
||||||
new_tree: &new_tree,
|
new_tree: &new_tree,
|
||||||
project,
|
project,
|
||||||
|
force_json,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut snapshots = vec![SyncbackSnapshot {
|
let mut snapshots = vec![SyncbackSnapshot {
|
||||||
@@ -197,7 +199,7 @@ pub fn syncback_loop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let middleware = get_best_middleware(&snapshot);
|
let middleware = get_best_middleware(&snapshot, force_json);
|
||||||
|
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"Middleware for {inst_path} is {:?} (path is {})",
|
"Middleware for {inst_path} is {:?} (path is {})",
|
||||||
@@ -213,10 +215,14 @@ pub fn syncback_loop(
|
|||||||
let syncback = match middleware.syncback(&snapshot) {
|
let syncback = match middleware.syncback(&snapshot) {
|
||||||
Ok(syncback) => syncback,
|
Ok(syncback) => syncback,
|
||||||
Err(err) if middleware == Middleware::Dir => {
|
Err(err) if middleware == Middleware::Dir => {
|
||||||
let new_middleware = match env::var(DEBUG_MODEL_FORMAT_VAR) {
|
let new_middleware = if force_json {
|
||||||
Ok(value) if value == "1" => Middleware::Rbxmx,
|
Middleware::JsonModel
|
||||||
Ok(value) if value == "2" => Middleware::JsonModel,
|
} else {
|
||||||
_ => Middleware::Rbxm,
|
match env::var(DEBUG_MODEL_FORMAT_VAR) {
|
||||||
|
Ok(value) if value == "1" => Middleware::Rbxmx,
|
||||||
|
Ok(value) if value == "2" => Middleware::JsonModel,
|
||||||
|
_ => Middleware::Rbxm,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let file_name = snapshot
|
let file_name = snapshot
|
||||||
.path
|
.path
|
||||||
@@ -295,7 +301,7 @@ pub struct SyncbackReturn<'sync> {
|
|||||||
pub removed_children: Vec<InstanceWithMeta<'sync>>,
|
pub removed_children: Vec<InstanceWithMeta<'sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_best_middleware(snapshot: &SyncbackSnapshot) -> Middleware {
|
pub fn get_best_middleware(snapshot: &SyncbackSnapshot, force_json: bool) -> Middleware {
|
||||||
// At some point, we're better off using an O(1) method for checking
|
// At some point, we're better off using an O(1) method for checking
|
||||||
// equality for classes like this.
|
// equality for classes like this.
|
||||||
static JSON_MODEL_CLASSES: OnceLock<HashSet<&str>> = OnceLock::new();
|
static JSON_MODEL_CLASSES: OnceLock<HashSet<&str>> = OnceLock::new();
|
||||||
@@ -361,10 +367,18 @@ pub fn get_best_middleware(snapshot: &SyncbackSnapshot) -> Middleware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if middleware == Middleware::Rbxm {
|
if middleware == Middleware::Rbxm {
|
||||||
middleware = match env::var(DEBUG_MODEL_FORMAT_VAR) {
|
middleware = if force_json {
|
||||||
Ok(value) if value == "1" => Middleware::Rbxmx,
|
if !inst.children().is_empty() {
|
||||||
Ok(value) if value == "2" => Middleware::JsonModel,
|
Middleware::Dir
|
||||||
_ => Middleware::Rbxm,
|
} else {
|
||||||
|
Middleware::JsonModel
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match env::var(DEBUG_MODEL_FORMAT_VAR) {
|
||||||
|
Ok(value) if value == "1" => Middleware::Rbxmx,
|
||||||
|
Ok(value) if value == "2" => Middleware::JsonModel,
|
||||||
|
_ => Middleware::Rbxm,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ use rbx_dom_weak::{
|
|||||||
ustr, Instance, Ustr, WeakDom,
|
ustr, Instance, Ustr, WeakDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{multimap::MultiMap, REF_ID_ATTRIBUTE_NAME, REF_POINTER_ATTRIBUTE_PREFIX};
|
use crate::{
|
||||||
|
multimap::MultiMap, syncback::snapshot::inst_path, REF_ID_ATTRIBUTE_NAME,
|
||||||
|
REF_POINTER_ATTRIBUTE_PREFIX,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct RefLinks {
|
pub struct RefLinks {
|
||||||
/// A map of referents to each of their Ref properties.
|
/// A map of referents to each of their Ref properties.
|
||||||
@@ -50,6 +53,22 @@ pub fn collect_referents(dom: &WeakDom) -> RefLinks {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let target = match dom.get_by_ref(*prop_value) {
|
||||||
|
Some(inst) => inst,
|
||||||
|
None => {
|
||||||
|
// Properties that are Some but point to nothing may as
|
||||||
|
// well be `nil`. Roblox and us never produce these values
|
||||||
|
// but syncback prunes trees without adjusting ref
|
||||||
|
// properties for performance reasons.
|
||||||
|
log::warn!(
|
||||||
|
"Property {}.{} will be `nil` on the disk because the actual value is not being included in syncback",
|
||||||
|
inst_path(dom, inst_ref),
|
||||||
|
prop_name
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
links.insert(
|
links.insert(
|
||||||
inst_ref,
|
inst_ref,
|
||||||
RefLink {
|
RefLink {
|
||||||
@@ -58,10 +77,6 @@ pub fn collect_referents(dom: &WeakDom) -> RefLinks {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let target = dom
|
|
||||||
.get_by_ref(*prop_value)
|
|
||||||
.expect("Refs in DOM should point to valid Instances");
|
|
||||||
|
|
||||||
// 1. Check if target has an ID
|
// 1. Check if target has an ID
|
||||||
if let Some(id) = get_existing_id(target) {
|
if let Some(id) = get_existing_id(target) {
|
||||||
// If it does, we need to check whether that ID is a duplicate
|
// If it does, we need to check whether that ID is a duplicate
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub struct SyncbackData<'sync> {
|
|||||||
pub(super) old_tree: &'sync RojoTree,
|
pub(super) old_tree: &'sync RojoTree,
|
||||||
pub(super) new_tree: &'sync WeakDom,
|
pub(super) new_tree: &'sync WeakDom,
|
||||||
pub(super) project: &'sync Project,
|
pub(super) project: &'sync Project,
|
||||||
|
pub(super) force_json: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SyncbackSnapshot<'sync> {
|
pub struct SyncbackSnapshot<'sync> {
|
||||||
@@ -43,7 +44,7 @@ impl<'sync> SyncbackSnapshot<'sync> {
|
|||||||
path: PathBuf::new(),
|
path: PathBuf::new(),
|
||||||
middleware: None,
|
middleware: None,
|
||||||
};
|
};
|
||||||
let middleware = get_best_middleware(&snapshot);
|
let middleware = get_best_middleware(&snapshot, self.data.force_json);
|
||||||
let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
|
let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
|
||||||
snapshot.path = self.path.join(name.as_ref());
|
snapshot.path = self.path.join(name.as_ref());
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ impl<'sync> SyncbackSnapshot<'sync> {
|
|||||||
path: PathBuf::new(),
|
path: PathBuf::new(),
|
||||||
middleware: None,
|
middleware: None,
|
||||||
};
|
};
|
||||||
let middleware = get_best_middleware(&snapshot);
|
let middleware = get_best_middleware(&snapshot, self.data.force_json);
|
||||||
let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
|
let name = name_for_inst(middleware, snapshot.new_inst(), snapshot.old_inst())?;
|
||||||
snapshot.path = base_path.join(name.as_ref());
|
snapshot.path = base_path.join(name.as_ref());
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
//! Defines Rojo's HTTP API, all under /api. These endpoints generally return
|
//! Defines Rojo's HTTP API, all under /api. These endpoints generally return
|
||||||
//! JSON.
|
//! JSON.
|
||||||
|
|
||||||
use std::{
|
use std::{collections::HashMap, fs, path::PathBuf, str::FromStr, sync::Arc};
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
fs,
|
|
||||||
path::PathBuf,
|
|
||||||
str::FromStr,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use futures::{sink::SinkExt, stream::StreamExt};
|
use futures::{sink::SinkExt, stream::StreamExt};
|
||||||
use hyper::{body, Body, Method, Request, Response, StatusCode};
|
use hyper::{body, Body, Method, Request, Response, StatusCode};
|
||||||
@@ -30,7 +24,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
util::{json, json_ok},
|
util::{json, json_ok},
|
||||||
},
|
},
|
||||||
web_api::{BufferEncode, InstanceUpdate, RefPatchResponse, SerializeResponse},
|
web_api::{
|
||||||
|
BufferEncode, InstanceUpdate, RefPatchRequest, RefPatchResponse, SerializeRequest,
|
||||||
|
SerializeResponse,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn call(serve_session: Arc<ServeSession>, mut request: Request<Body>) -> Response<Body> {
|
pub async fn call(serve_session: Arc<ServeSession>, mut request: Request<Body>) -> Response<Body> {
|
||||||
@@ -53,12 +50,8 @@ pub async fn call(serve_session: Arc<ServeSession>, mut request: Request<Body>)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(&Method::GET, path) if path.starts_with("/api/serialize/") => {
|
(&Method::POST, "/api/serialize") => service.handle_api_serialize(request).await,
|
||||||
service.handle_api_serialize(request).await
|
(&Method::POST, "/api/ref-patch") => service.handle_api_ref_patch(request).await,
|
||||||
}
|
|
||||||
(&Method::GET, path) if path.starts_with("/api/ref-patch/") => {
|
|
||||||
service.handle_api_ref_patch(request).await
|
|
||||||
}
|
|
||||||
|
|
||||||
(&Method::POST, path) if path.starts_with("/api/open/") => {
|
(&Method::POST, path) if path.starts_with("/api/open/") => {
|
||||||
service.handle_api_open(request).await
|
service.handle_api_open(request).await
|
||||||
@@ -229,22 +222,30 @@ impl ApiService {
|
|||||||
/// that correspond to the requested Instances. These values have their
|
/// that correspond to the requested Instances. These values have their
|
||||||
/// `Value` property set to point to the requested Instance.
|
/// `Value` property set to point to the requested Instance.
|
||||||
async fn handle_api_serialize(&self, request: Request<Body>) -> Response<Body> {
|
async fn handle_api_serialize(&self, request: Request<Body>) -> Response<Body> {
|
||||||
let argument = &request.uri().path()["/api/serialize/".len()..];
|
let session_id = self.serve_session.session_id();
|
||||||
let requested_ids: Result<Vec<Ref>, _> = argument.split(',').map(Ref::from_str).collect();
|
let body = body::to_bytes(request.into_body()).await.unwrap();
|
||||||
|
|
||||||
let requested_ids = match requested_ids {
|
let request: SerializeRequest = match json::from_slice(&body) {
|
||||||
Ok(ids) => ids,
|
Ok(request) => request,
|
||||||
Err(_) => {
|
Err(err) => {
|
||||||
return json(
|
return json(
|
||||||
ErrorResponse::bad_request("Malformed ID list"),
|
ErrorResponse::bad_request(format!("Invalid body: {}", err)),
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if request.session_id != session_id {
|
||||||
|
return json(
|
||||||
|
ErrorResponse::bad_request("Wrong session ID"),
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let mut response_dom = WeakDom::new(InstanceBuilder::new("Folder"));
|
let mut response_dom = WeakDom::new(InstanceBuilder::new("Folder"));
|
||||||
|
|
||||||
let tree = self.serve_session.tree();
|
let tree = self.serve_session.tree();
|
||||||
for id in &requested_ids {
|
for id in &request.ids {
|
||||||
if let Some(instance) = tree.get_instance(*id) {
|
if let Some(instance) = tree.get_instance(*id) {
|
||||||
let clone = response_dom.insert(
|
let clone = response_dom.insert(
|
||||||
Ref::none(),
|
Ref::none(),
|
||||||
@@ -290,20 +291,26 @@ impl ApiService {
|
|||||||
/// and referent properties need to be updated after the serialize
|
/// and referent properties need to be updated after the serialize
|
||||||
/// endpoint is used.
|
/// endpoint is used.
|
||||||
async fn handle_api_ref_patch(self, request: Request<Body>) -> Response<Body> {
|
async fn handle_api_ref_patch(self, request: Request<Body>) -> Response<Body> {
|
||||||
let argument = &request.uri().path()["/api/ref-patch/".len()..];
|
let session_id = self.serve_session.session_id();
|
||||||
let requested_ids: Result<HashSet<Ref>, _> =
|
let body = body::to_bytes(request.into_body()).await.unwrap();
|
||||||
argument.split(',').map(Ref::from_str).collect();
|
|
||||||
|
|
||||||
let requested_ids = match requested_ids {
|
let request: RefPatchRequest = match json::from_slice(&body) {
|
||||||
Ok(ids) => ids,
|
Ok(request) => request,
|
||||||
Err(_) => {
|
Err(err) => {
|
||||||
return json(
|
return json(
|
||||||
ErrorResponse::bad_request("Malformed ID list"),
|
ErrorResponse::bad_request(format!("Invalid body: {}", err)),
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if request.session_id != session_id {
|
||||||
|
return json(
|
||||||
|
ErrorResponse::bad_request("Wrong session ID"),
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let mut instance_updates: HashMap<Ref, InstanceUpdate> = HashMap::new();
|
let mut instance_updates: HashMap<Ref, InstanceUpdate> = HashMap::new();
|
||||||
|
|
||||||
let tree = self.serve_session.tree();
|
let tree = self.serve_session.tree();
|
||||||
@@ -312,7 +319,7 @@ impl ApiService {
|
|||||||
let Variant::Ref(prop_value) = prop_value else {
|
let Variant::Ref(prop_value) = prop_value else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if let Some(target_id) = requested_ids.get(prop_value) {
|
if let Some(target_id) = request.ids.get(prop_value) {
|
||||||
let instance_id = instance.id();
|
let instance_id = instance.id();
|
||||||
let update =
|
let update =
|
||||||
instance_updates
|
instance_updates
|
||||||
|
|||||||
@@ -238,6 +238,13 @@ pub struct OpenResponse {
|
|||||||
pub session_id: SessionId,
|
pub session_id: SessionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SerializeRequest {
|
||||||
|
pub session_id: SessionId,
|
||||||
|
pub ids: Vec<Ref>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SerializeResponse {
|
pub struct SerializeResponse {
|
||||||
@@ -269,6 +276,13 @@ impl BufferEncode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RefPatchRequest {
|
||||||
|
pub session_id: SessionId,
|
||||||
|
pub ids: HashSet<Ref>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RefPatchResponse<'a> {
|
pub struct RefPatchResponse<'a> {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fmt::Write as _,
|
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command,
|
process::Command,
|
||||||
@@ -13,8 +12,12 @@ use rbx_dom_weak::types::Ref;
|
|||||||
|
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
|
|
||||||
use librojo::web_api::{
|
use librojo::{
|
||||||
ReadResponse, SerializeResponse, ServerInfoResponse, SocketPacket, SocketPacketType,
|
web_api::{
|
||||||
|
ReadResponse, SerializeRequest, SerializeResponse, ServerInfoResponse, SocketPacket,
|
||||||
|
SocketPacketType,
|
||||||
|
},
|
||||||
|
SessionId,
|
||||||
};
|
};
|
||||||
use rojo_insta_ext::RedactionMap;
|
use rojo_insta_ext::RedactionMap;
|
||||||
|
|
||||||
@@ -226,16 +229,19 @@ impl TestServeSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_api_serialize(&self, ids: &[Ref]) -> Result<SerializeResponse, reqwest::Error> {
|
pub fn get_api_serialize(
|
||||||
let mut id_list = String::with_capacity(ids.len() * 33);
|
&self,
|
||||||
for id in ids {
|
ids: &[Ref],
|
||||||
write!(id_list, "{id},").unwrap();
|
session_id: SessionId,
|
||||||
}
|
) -> Result<SerializeResponse, reqwest::Error> {
|
||||||
id_list.pop();
|
let client = reqwest::blocking::Client::new();
|
||||||
|
let url = format!("http://localhost:{}/api/serialize", self.port);
|
||||||
|
let body = serde_json::to_string(&SerializeRequest {
|
||||||
|
session_id,
|
||||||
|
ids: ids.to_vec(),
|
||||||
|
});
|
||||||
|
|
||||||
let url = format!("http://localhost:{}/api/serialize/{}", self.port, id_list);
|
client.post(url).body((body).unwrap()).send()?.json()
|
||||||
|
|
||||||
reqwest::blocking::get(url)?.json()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -646,7 +646,7 @@ fn meshpart_with_id() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let serialize_response = session
|
let serialize_response = session
|
||||||
.get_api_serialize(&[*meshpart, *objectvalue])
|
.get_api_serialize(&[*meshpart, *objectvalue], info.session_id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// We don't assert a snapshot on the SerializeResponse because the model includes the
|
// We don't assert a snapshot on the SerializeResponse because the model includes the
|
||||||
@@ -673,7 +673,9 @@ fn forced_parent() {
|
|||||||
read_response.intern_and_redact(&mut redactions, root_id)
|
read_response.intern_and_redact(&mut redactions, root_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
let serialize_response = session.get_api_serialize(&[root_id]).unwrap();
|
let serialize_response = session
|
||||||
|
.get_api_serialize(&[root_id], info.session_id)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(serialize_response.session_id, info.session_id);
|
assert_eq!(serialize_response.session_id, info.session_id);
|
||||||
|
|
||||||
|
|||||||
@@ -71,8 +71,13 @@ syncback_tests! {
|
|||||||
ref_properties_conflict => ["src/Pointer_2.model.json", "src/Target_2.model.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.
|
// Ensures that having multiple pointers that are aimed at the same target doesn't trigger ref rewrites.
|
||||||
ref_properties_duplicate => [],
|
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"],
|
||||||
// Ensures that the old middleware is respected during syncback
|
// 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"],
|
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
|
||||||
|
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
|
// Ensures that StringValues inside project files are written to the
|
||||||
// project file, but only if they don't have `$path` set
|
// project file, but only if they don't have `$path` set
|
||||||
string_value_project => ["default.project.json"],
|
string_value_project => ["default.project.json"],
|
||||||
@@ -81,5 +86,4 @@ syncback_tests! {
|
|||||||
sync_rules => ["src/module.modulescript", "src/text.text"],
|
sync_rules => ["src/module.modulescript", "src/text.text"],
|
||||||
// Ensures that the `syncUnscriptable` setting works
|
// Ensures that the `syncUnscriptable` setting works
|
||||||
unscriptable_properties => ["default.project.json"],
|
unscriptable_properties => ["default.project.json"],
|
||||||
schema_roundtrip => ["default.project.json", "src/model.model.json", "src/init/init.meta.json", "src/adjacent.meta.json"]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user