-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[TS-2073] Server implementation for tooling interop #11
base: develop
Are you sure you want to change the base?
Changes from 2 commits
0a18a6b
09d8942
074526b
6d985ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ mod error; | |
mod game; | ||
mod package; | ||
mod project; | ||
mod server; | ||
mod ts; | ||
mod ui; | ||
mod util; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
use std::fs::{self, File}; | ||
use std::path::{Path, PathBuf}; | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
pub enum Error { | ||
#[error("The lock at {0:?} is already acquired by a process with PID {1}.")] | ||
InUse(PathBuf, u32), | ||
} | ||
|
||
/// Only one server process can "own" a project at a single time. | ||
/// We enforce this exclusive access through the creation and deletion of this lockfile. | ||
const LOCKFILE: &'static str = ".server-lock"; | ||
|
||
pub struct ProjectLock<'a> { | ||
file: File, | ||
path: &'a Path, | ||
} | ||
|
||
impl<'a> ProjectLock<'_> { | ||
/// Attempt to acquire a lock on the provided directory. | ||
pub fn lock(project_path: &'a Path) -> Result<Self, Error> { | ||
todo!() | ||
} | ||
} | ||
|
||
impl Drop for ProjectLock<'_> { | ||
fn drop(&mut self) { | ||
// The file handle is dropped before this, so we can safely delete it. | ||
fs::remove_file(self.path); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
pub mod package; | ||
pub mod project; | ||
|
||
use serde::de::DeserializeOwned; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
use self::package::PackageMethod; | ||
use self::project::ProjectMethod; | ||
use super::Error; | ||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] | ||
pub enum Method { | ||
Exit, | ||
Project(ProjectMethod), | ||
Package(PackageMethod), | ||
} | ||
|
||
/// Method namespace registration. | ||
impl Method { | ||
pub fn from_value(method: &str, value: serde_json::Value) -> Result<Self, Error> { | ||
let mut split = method.split('/'); | ||
let (namespace, name) = ( | ||
split | ||
.next() | ||
.ok_or_else(|| Error::InvalidMethod(method.into()))?, | ||
split | ||
.next() | ||
.ok_or_else(|| Error::InvalidMethod(method.into()))?, | ||
); | ||
|
||
// Route namespaces to the appropriate enum variants for construction. | ||
Ok(match namespace { | ||
"exit" => Self::Exit, | ||
"project" => Self::Project(ProjectMethod::from_value(name, value)?), | ||
"package" => Self::Package(PackageMethod::from_value(name, value)?), | ||
x => Err(Error::InvalidMethod(x.into()))?, | ||
}) | ||
} | ||
} | ||
|
||
pub fn parse_value<T: DeserializeOwned>(value: serde_json::Value) -> Result<T, serde_json::Error> { | ||
serde_json::from_value(value) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
use serde::{Deserialize, Serialize}; | ||
|
||
use super::Error; | ||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] | ||
pub enum PackageMethod { | ||
/// Get metadata about this package. | ||
GetMetadata, | ||
/// Determine if the package exists within the cache. | ||
IsCached, | ||
} | ||
|
||
impl PackageMethod { | ||
pub fn from_value(method: &str, value: serde_json::Value) -> Result<Self, Error> { | ||
Check warning Code scanning / clippy unused variable: value Warning
unused variable: value
|
||
Ok(match method { | ||
"get_metadata" => Self::GetMetadata, | ||
"is_cached" => Self::IsCached, | ||
x => Err(Error::InvalidMethod(x.into()))?, | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
use std::path::PathBuf; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
use crate::ts::package_reference::PackageReference; | ||
|
||
use super::Error; | ||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] | ||
pub enum ProjectMethod { | ||
/// Set the project directory context. This locks the project, if it exists. | ||
SetContext(SetContext), | ||
/// Release the project directory context. This unlocks the project, if a lock exists. | ||
ReleaseContext, | ||
/// Get project metadata. | ||
GetMetadata, | ||
/// Add one or more packages to the project. | ||
AddPackages(AddPackages), | ||
/// Remove one or more packages from the project. | ||
RemovePackages(RemovePackages), | ||
/// Get a list of currently installed packages. | ||
GetPackages, | ||
/// Determine if the current context is a valid project. | ||
IsValid, | ||
} | ||
|
||
impl ProjectMethod { | ||
pub fn from_value(method: &str, value: serde_json::Value) -> Result<Self, Error> { | ||
Ok(match method { | ||
"set_context" => Self::SetContext(super::parse_value(value)?), | ||
"release_context" => Self::ReleaseContext, | ||
"get_metadata" => Self::GetMetadata, | ||
"add_packages" => Self::AddPackages(super::parse_value(value)?), | ||
"remove_packages" => Self::RemovePackages(super::parse_value(value)?), | ||
"get_packages" => Self::GetPackages, | ||
"is_valid" => Self::IsValid, | ||
x => Err(Error::InvalidMethod(x.into()))?, | ||
}) | ||
} | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] | ||
pub struct SetContext { | ||
path: PathBuf, | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] | ||
pub struct AddPackages { | ||
packages: Vec<PackageReference>, | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] | ||
pub struct RemovePackages { | ||
packages: Vec<PackageReference>, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
use std::io::Write; | ||
use std::sync::mpsc::{self, Receiver, Sender}; | ||
use std::sync::RwLock; | ||
use std::{io, thread}; | ||
|
||
use self::proto::{Message, Request, Response}; | ||
Check warning Code scanning / clippy unused import: Request Warning
unused import: Request
|
||
|
||
mod method; | ||
mod proto; | ||
mod route; | ||
|
||
trait ToJson { | ||
fn to_json(&self) -> Result<String, serde_json::Error>; | ||
} | ||
|
||
/// This error type exists to wrap library errors into a single easy-to-use package. | ||
#[derive(thiserror::Error, Debug)] | ||
#[repr(isize)] | ||
pub enum Error { | ||
/// A partial implementation of the error variants described by the JRPC spec. | ||
#[error("Failed to serialize JSON: {0:?}")] | ||
InvalidJson(#[from] serde_json::Error) = -32700, | ||
|
||
#[error("The method {0} is not valid.")] | ||
InvalidMethod(String) = -32601, | ||
|
||
#[error("Recieved invalid params for method {0}: {1}")] | ||
InvalidParams(String, String) = -32602, | ||
|
||
/// Wrapper error types and codes. | ||
#[error("${0:?}")] | ||
ProjectError(String) = 1000, | ||
Check warning Code scanning / clippy variant name ends with the enum's name Warning
variant name ends with the enum's name
|
||
|
||
#[error("{0:?}")] | ||
PackageError(String) = 2000, | ||
Check warning Code scanning / clippy variant name ends with the enum's name Warning
variant name ends with the enum's name
|
||
} | ||
|
||
impl Error { | ||
pub fn discriminant(&self) -> isize { | ||
// SAFETY: `Self` is `repr(isize)` with layout `repr(C)`, with each variant having an isize | ||
// as its first field, so we can access this value without a pointer offset. | ||
unsafe { *<*const _>::from(self).cast::<isize>() } | ||
} | ||
} | ||
|
||
impl ToJson for Result<Response, Error> { | ||
fn to_json(&self) -> Result<String, serde_json::Error> { | ||
todo!() | ||
} | ||
} | ||
|
||
/// The daemon's entrypoint. This is a psuedo event loop which does the following in step: | ||
/// 1. Read JSON-RPC input(s) from stdin. | ||
/// 2. Route each input. | ||
/// 3. Serialize the output and write to stdout. | ||
async fn start() { | ||
let stdin = io::stdin(); | ||
let mut line = String::new(); | ||
let (tx, rx) = mpsc::channel::<Result<Response, Error>>(); | ||
|
||
let cancel = RwLock::new(false); | ||
|
||
// Responses are published through the tx send channel. | ||
thread::spawn(move || respond_msg(rx, cancel)); | ||
|
||
loop { | ||
// Block the main thread until we have an input line available to be read. | ||
// This is ok because, in theory, tasks will be processed on background threads. | ||
if let Err(e) = stdin.read_line(&mut line) { | ||
Check warning Code scanning / clippy unused variable: e Warning
unused variable: e
|
||
panic!("") | ||
} | ||
let res = route(&line, tx.clone()).await; | ||
res.to_json().unwrap(); | ||
} | ||
} | ||
|
||
fn respond_msg(rx: Receiver<Result<Response, Error>>, cancel: RwLock<bool>) { | ||
Check warning Code scanning / clippy unused variable: cancel Warning
unused variable: cancel
|
||
let mut stdout = io::stdout(); | ||
while let Ok(res) = rx.recv() { | ||
let msg = res.map(|x| serde_json::to_string(&x).unwrap()); | ||
stdout.write_all(msg.unwrap().as_bytes()); | ||
Check warning Code scanning / clippy unused std::result::Result that must be used Warning
unused std::result::Result that must be used
|
||
stdout.write_all("\n".as_bytes()); | ||
Check warning Code scanning / clippy unused std::result::Result that must be used Warning
unused std::result::Result that must be used
|
||
} | ||
} | ||
|
||
/// Route and execute the request, returning the result. | ||
async fn route(line: &str, tx: Sender<Result<Response, Error>>) -> Result<Response, Error> { | ||
Check warning Code scanning / clippy unused variable: tx Warning
unused variable: tx
|
||
let req = Message::from_json(line); | ||
Check warning Code scanning / clippy unused variable: req Warning
unused variable: req
|
||
todo!() | ||
} |
Check warning
Code scanning / clippy
constants have by default a 'static lifetime Warning