Implement /api/open/{id} to open a script by ID in your default editor

This commit is contained in:
Lucien Greathouse
2020-03-17 17:50:54 -07:00
parent aa4039a2e7
commit 102c77b23e
2 changed files with 86 additions and 4 deletions

View File

@@ -1,7 +1,7 @@
//! Defines Rojo's HTTP API, all under /api. These endpoints generally return
//! JSON.
use std::{collections::HashMap, sync::Arc};
use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
use futures::{Future, Stream};
@@ -10,12 +10,12 @@ use rbx_dom_weak::RbxId;
use crate::{
serve_session::ServeSession,
snapshot::{PatchSet, PatchUpdate},
snapshot::{InstanceWithMeta, PatchSet, PatchUpdate},
web::{
interface::{
ErrorResponse, Instance, InstanceMetadata as WebInstanceMetadata, InstanceUpdate,
ReadResponse, ServerInfoResponse, SubscribeMessage, SubscribeResponse, WriteRequest,
WriteResponse, PROTOCOL_VERSION, SERVER_VERSION,
OpenResponse, ReadResponse, ServerInfoResponse, SubscribeMessage, SubscribeResponse,
WriteRequest, WriteResponse, PROTOCOL_VERSION, SERVER_VERSION,
},
util::{json, json_ok},
},
@@ -39,6 +39,9 @@ impl Service for ApiService {
(&Method::GET, path) if path.starts_with("/api/subscribe/") => {
self.handle_api_subscribe(request)
}
(&Method::POST, path) if path.starts_with("/api/open/") => {
self.handle_api_open(request)
}
(&Method::POST, "/api/write") if cfg!(feature = "unstable_two_way_sync") => {
self.handle_api_write(request)
@@ -233,4 +236,76 @@ impl ApiService {
instances,
})
}
/// Open a script with the given ID in the user's default text editor.
fn handle_api_open(&self, request: Request<Body>) -> <Self as Service>::Future {
let argument = &request.uri().path()["/api/open/".len()..];
let requested_id = match RbxId::parse_str(argument) {
Some(id) => id,
None => {
return json(
ErrorResponse::bad_request("Invalid instance ID"),
StatusCode::BAD_REQUEST,
);
}
};
let tree = self.serve_session.tree();
let instance = match tree.get_instance(requested_id) {
Some(instance) => instance,
None => {
return json(
ErrorResponse::bad_request("Instance not found"),
StatusCode::NOT_FOUND,
);
}
};
let script_path = match pick_script_path(instance) {
Some(path) => path,
None => {
return json(
ErrorResponse::bad_request(
"No appropriate file could be found to open this script",
),
StatusCode::NOT_FOUND,
);
}
};
let _ = opener::open(script_path);
json_ok(&OpenResponse {
session_id: self.serve_session.session_id(),
})
}
}
/// If this instance is represented by a script, try to find the correct .lua
/// file to open to edit it.
fn pick_script_path(instance: InstanceWithMeta<'_>) -> Option<PathBuf> {
match instance.class_name() {
"Script" | "LocalScript" | "ModuleScript" => {}
_ => return None,
}
// Pick the first listed relevant path that has an extension of .lua that
// exists.
instance
.metadata()
.relevant_paths
.iter()
.find(|path| {
// We should only ever open Lua files to be safe.
match path.extension().and_then(|ext| ext.to_str()) {
Some("lua") => {}
_ => return false,
}
fs::metadata(path)
.map(|meta| meta.is_file())
.unwrap_or(false)
})
.map(|path| path.to_owned())
}

View File

@@ -128,6 +128,13 @@ pub struct SubscribeResponse<'a> {
pub messages: Vec<SubscribeMessage<'a>>,
}
/// Response body from /api/open/{id}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenResponse {
pub session_id: SessionId,
}
/// General response type returned from all Rojo routes
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]