mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 14:01:37 +00:00
Use msgpack for API
This commit is contained in:
parent
3d328c1d42
commit
bda2af3dfb
12 changed files with 264 additions and 172 deletions
116
src/web/api.rs
116
src/web/api.rs
|
|
@ -19,7 +19,6 @@ use rbx_dom_weak::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
json,
|
||||
serve_session::ServeSession,
|
||||
snapshot::{InstanceWithMeta, PatchSet, PatchUpdate},
|
||||
web::{
|
||||
|
|
@ -28,12 +27,9 @@ use crate::{
|
|||
ServerInfoResponse, SocketPacket, SocketPacketBody, SocketPacketType, SubscribeMessage,
|
||||
WriteRequest, WriteResponse, PROTOCOL_VERSION, SERVER_VERSION,
|
||||
},
|
||||
util::{json_ok, json_response},
|
||||
},
|
||||
web_api::{
|
||||
BufferEncode, InstanceUpdate, RefPatchRequest, RefPatchResponse, SerializeRequest,
|
||||
SerializeResponse,
|
||||
util::{deserialize_msgpack, msgpack, msgpack_ok, serialize_msgpack},
|
||||
},
|
||||
web_api::{InstanceUpdate, RefPatchResponse, SerializeResponse},
|
||||
};
|
||||
|
||||
pub async fn call(serve_session: Arc<ServeSession>, mut request: Request<Body>) -> Response<Body> {
|
||||
|
|
@ -48,7 +44,7 @@ pub async fn call(serve_session: Arc<ServeSession>, mut request: Request<Body>)
|
|||
if is_upgrade_request(&request) {
|
||||
service.handle_api_socket(&mut request).await
|
||||
} else {
|
||||
json_response(
|
||||
msgpack(
|
||||
ErrorResponse::bad_request(
|
||||
"/api/socket must be called as a websocket upgrade request",
|
||||
),
|
||||
|
|
@ -56,15 +52,19 @@ pub async fn call(serve_session: Arc<ServeSession>, mut request: Request<Body>)
|
|||
)
|
||||
}
|
||||
}
|
||||
(&Method::POST, "/api/serialize") => 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/serialize/") => {
|
||||
service.handle_api_serialize(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/") => {
|
||||
service.handle_api_open(request).await
|
||||
}
|
||||
(&Method::POST, "/api/write") => service.handle_api_write(request).await,
|
||||
|
||||
(_method, path) => json_response(
|
||||
(_method, path) => msgpack(
|
||||
ErrorResponse::not_found(format!("Route not found: {}", path)),
|
||||
StatusCode::NOT_FOUND,
|
||||
),
|
||||
|
|
@ -85,7 +85,7 @@ impl ApiService {
|
|||
let tree = self.serve_session.tree();
|
||||
let root_instance_id = tree.get_root_id();
|
||||
|
||||
json_ok(&ServerInfoResponse {
|
||||
msgpack_ok(&ServerInfoResponse {
|
||||
server_version: SERVER_VERSION.to_owned(),
|
||||
protocol_version: PROTOCOL_VERSION,
|
||||
session_id: self.serve_session.session_id(),
|
||||
|
|
@ -105,7 +105,7 @@ impl ApiService {
|
|||
let input_cursor: u32 = match argument.parse() {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return json_response(
|
||||
return msgpack(
|
||||
ErrorResponse::bad_request(format!("Malformed message cursor: {}", err)),
|
||||
StatusCode::BAD_REQUEST,
|
||||
);
|
||||
|
|
@ -116,7 +116,7 @@ impl ApiService {
|
|||
let (response, websocket) = match upgrade(request, None) {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
return json_response(
|
||||
return msgpack(
|
||||
ErrorResponse::internal_error(format!("WebSocket upgrade failed: {}", err)),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
|
|
@ -143,10 +143,10 @@ impl ApiService {
|
|||
|
||||
let body = body::to_bytes(request.into_body()).await.unwrap();
|
||||
|
||||
let request: WriteRequest = match json::from_slice(&body) {
|
||||
let request: WriteRequest = match deserialize_msgpack(&body) {
|
||||
Ok(request) => request,
|
||||
Err(err) => {
|
||||
return json_response(
|
||||
return msgpack(
|
||||
ErrorResponse::bad_request(format!("Invalid body: {}", err)),
|
||||
StatusCode::BAD_REQUEST,
|
||||
);
|
||||
|
|
@ -154,7 +154,7 @@ impl ApiService {
|
|||
};
|
||||
|
||||
if request.session_id != session_id {
|
||||
return json_response(
|
||||
return msgpack(
|
||||
ErrorResponse::bad_request("Wrong session ID"),
|
||||
StatusCode::BAD_REQUEST,
|
||||
);
|
||||
|
|
@ -180,7 +180,7 @@ impl ApiService {
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
json_ok(WriteResponse { session_id })
|
||||
msgpack_ok(WriteResponse { session_id })
|
||||
}
|
||||
|
||||
async fn handle_api_read(&self, request: Request<Body>) -> Response<Body> {
|
||||
|
|
@ -190,7 +190,7 @@ impl ApiService {
|
|||
let requested_ids = match requested_ids {
|
||||
Ok(ids) => ids,
|
||||
Err(_) => {
|
||||
return json_response(
|
||||
return msgpack(
|
||||
ErrorResponse::bad_request("Malformed ID list"),
|
||||
StatusCode::BAD_REQUEST,
|
||||
);
|
||||
|
|
@ -254,7 +254,7 @@ impl ApiService {
|
|||
}
|
||||
}
|
||||
|
||||
json_ok(ReadResponse {
|
||||
msgpack_ok(ReadResponse {
|
||||
session_id: self.serve_session.session_id(),
|
||||
message_cursor,
|
||||
instances,
|
||||
|
|
@ -269,30 +269,23 @@ impl ApiService {
|
|||
/// that correspond to the requested Instances. These values have their
|
||||
/// `Value` property set to point to the requested Instance.
|
||||
async fn handle_api_serialize(&self, request: Request<Body>) -> Response<Body> {
|
||||
let session_id = self.serve_session.session_id();
|
||||
let body = body::to_bytes(request.into_body()).await.unwrap();
|
||||
let argument = &request.uri().path()["/api/serialize/".len()..];
|
||||
let requested_ids: Result<Vec<Ref>, _> = argument.split(',').map(Ref::from_str).collect();
|
||||
|
||||
let request: SerializeRequest = match json::from_slice(&body) {
|
||||
Ok(request) => request,
|
||||
Err(err) => {
|
||||
return json_response(
|
||||
ErrorResponse::bad_request(format!("Invalid body: {}", err)),
|
||||
let requested_ids = match requested_ids {
|
||||
Ok(ids) => ids,
|
||||
Err(_) => {
|
||||
return msgpack(
|
||||
ErrorResponse::bad_request("Malformed ID list"),
|
||||
StatusCode::BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if request.session_id != session_id {
|
||||
return json_response(
|
||||
ErrorResponse::bad_request("Wrong session ID"),
|
||||
StatusCode::BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
let mut response_dom = WeakDom::new(InstanceBuilder::new("Folder"));
|
||||
|
||||
let tree = self.serve_session.tree();
|
||||
for id in &request.ids {
|
||||
for id in &requested_ids {
|
||||
if let Some(instance) = tree.get_instance(*id) {
|
||||
let clone = response_dom.insert(
|
||||
Ref::none(),
|
||||
|
|
@ -316,7 +309,7 @@ impl ApiService {
|
|||
|
||||
response_dom.transfer_within(child_ref, object_value);
|
||||
} else {
|
||||
json_response(
|
||||
msgpack(
|
||||
ErrorResponse::bad_request(format!("provided id {id} is not in the tree")),
|
||||
StatusCode::BAD_REQUEST,
|
||||
);
|
||||
|
|
@ -327,9 +320,9 @@ impl ApiService {
|
|||
let mut source = Vec::new();
|
||||
rbx_binary::to_writer(&mut source, &response_dom, &[response_dom.root_ref()]).unwrap();
|
||||
|
||||
json_ok(SerializeResponse {
|
||||
msgpack_ok(SerializeResponse {
|
||||
session_id: self.serve_session.session_id(),
|
||||
model_contents: BufferEncode::new(source),
|
||||
model_contents: source,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -337,26 +330,22 @@ impl ApiService {
|
|||
/// provided IDs. Used because the plugin does not store a RojoTree,
|
||||
/// and referent properties need to be updated after the serialize
|
||||
/// endpoint is used.
|
||||
async fn handle_api_ref_patch(self, request: Request<Body>) -> Response<Body> {
|
||||
let session_id = self.serve_session.session_id();
|
||||
let body = body::to_bytes(request.into_body()).await.unwrap();
|
||||
async fn handle_api_ref_patch(&self, request: Request<Body>) -> Response<Body> {
|
||||
let argument = &request.uri().path()["/api/ref-patch/".len()..];
|
||||
let requested_ids: Result<Vec<Ref>, _> = argument.split(',').map(Ref::from_str).collect();
|
||||
|
||||
let request: RefPatchRequest = match json::from_slice(&body) {
|
||||
Ok(request) => request,
|
||||
Err(err) => {
|
||||
return json_response(
|
||||
ErrorResponse::bad_request(format!("Invalid body: {}", err)),
|
||||
let requested_ids = match requested_ids {
|
||||
Ok(ids) => ids,
|
||||
Err(_) => {
|
||||
return msgpack(
|
||||
ErrorResponse::bad_request("Malformed ID list"),
|
||||
StatusCode::BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if request.session_id != session_id {
|
||||
return json_response(
|
||||
ErrorResponse::bad_request("Wrong session ID"),
|
||||
StatusCode::BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
// Convert to HashSet for efficient lookup
|
||||
let ids: HashSet<Ref> = requested_ids.into_iter().collect();
|
||||
|
||||
let mut instance_updates: HashMap<Ref, InstanceUpdate> = HashMap::new();
|
||||
|
||||
|
|
@ -366,7 +355,7 @@ impl ApiService {
|
|||
let Variant::Ref(prop_value) = prop_value else {
|
||||
continue;
|
||||
};
|
||||
if let Some(target_id) = request.ids.get(prop_value) {
|
||||
if let Some(target_id) = ids.get(prop_value) {
|
||||
let instance_id = instance.id();
|
||||
let update =
|
||||
instance_updates
|
||||
|
|
@ -385,7 +374,7 @@ impl ApiService {
|
|||
}
|
||||
}
|
||||
|
||||
json_ok(RefPatchResponse {
|
||||
msgpack_ok(RefPatchResponse {
|
||||
session_id: self.serve_session.session_id(),
|
||||
patch: SubscribeMessage {
|
||||
added: HashMap::new(),
|
||||
|
|
@ -401,7 +390,7 @@ impl ApiService {
|
|||
let requested_id = match Ref::from_str(argument) {
|
||||
Ok(id) => id,
|
||||
Err(_) => {
|
||||
return json_response(
|
||||
return msgpack(
|
||||
ErrorResponse::bad_request("Invalid instance ID"),
|
||||
StatusCode::BAD_REQUEST,
|
||||
);
|
||||
|
|
@ -413,7 +402,7 @@ impl ApiService {
|
|||
let instance = match tree.get_instance(requested_id) {
|
||||
Some(instance) => instance,
|
||||
None => {
|
||||
return json_response(
|
||||
return msgpack(
|
||||
ErrorResponse::bad_request("Instance not found"),
|
||||
StatusCode::NOT_FOUND,
|
||||
);
|
||||
|
|
@ -423,7 +412,7 @@ impl ApiService {
|
|||
let script_path = match pick_script_path(instance) {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
return json_response(
|
||||
return msgpack(
|
||||
ErrorResponse::bad_request(
|
||||
"No appropriate file could be found to open this script",
|
||||
),
|
||||
|
|
@ -436,7 +425,7 @@ impl ApiService {
|
|||
Ok(()) => {}
|
||||
Err(error) => match error {
|
||||
OpenError::Io(io_error) => {
|
||||
return json_response(
|
||||
return msgpack(
|
||||
ErrorResponse::internal_error(format!(
|
||||
"Attempting to open {} failed because of the following io error: {}",
|
||||
script_path.display(),
|
||||
|
|
@ -450,7 +439,7 @@ impl ApiService {
|
|||
status,
|
||||
stderr,
|
||||
} => {
|
||||
return json_response(
|
||||
return msgpack(
|
||||
ErrorResponse::internal_error(format!(
|
||||
r#"The command '{}' to open '{}' failed with the error code '{}'.
|
||||
Error logs:
|
||||
|
|
@ -466,7 +455,7 @@ impl ApiService {
|
|||
},
|
||||
};
|
||||
|
||||
json_ok(OpenResponse {
|
||||
msgpack_ok(OpenResponse {
|
||||
session_id: self.serve_session.session_id(),
|
||||
})
|
||||
}
|
||||
|
|
@ -531,7 +520,7 @@ async fn handle_websocket_subscription(
|
|||
match result {
|
||||
Ok((new_cursor, messages)) => {
|
||||
if !messages.is_empty() {
|
||||
let json_message = {
|
||||
let msgpack_message = {
|
||||
let tree = tree_handle.lock().unwrap();
|
||||
let api_messages: Vec<_> = messages
|
||||
.into_iter()
|
||||
|
|
@ -555,13 +544,12 @@ async fn handle_websocket_subscription(
|
|||
}),
|
||||
};
|
||||
|
||||
// Use compact JSON5 encoder for transport efficiency, preserves NaN/Infinity
|
||||
json::to_string_compact(&response)?
|
||||
serialize_msgpack(response)?
|
||||
};
|
||||
|
||||
log::debug!("Sending batch of messages over WebSocket subscription");
|
||||
|
||||
if websocket.send(Message::Text(json_message)).await.is_err() {
|
||||
if websocket.send(Message::Binary(msgpack_message)).await.is_err() {
|
||||
// Client disconnected
|
||||
log::debug!("WebSocket subscription closed by client");
|
||||
break;
|
||||
|
|
@ -677,7 +665,7 @@ fn filter_subscribe_message_for_scripts(
|
|||
// ALL instances get ignoreUnknownInstances: true in scripts-only mode
|
||||
// This ensures we only sync script content, never delete/modify other instances
|
||||
// Non-scripts also have their properties cleared - they're just tree structure
|
||||
for (_id, inst) in &mut msg.added {
|
||||
for inst in msg.added.values_mut() {
|
||||
inst.metadata = Some(InstanceMetadata {
|
||||
ignore_unknown_instances: true,
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue