Skip to content

Commit 09d8942

Browse files
refactor methods into submodule, impl simple msg loop
1 parent 0a18a6b commit 09d8942

File tree

8 files changed

+376
-196
lines changed

8 files changed

+376
-196
lines changed

src/server/lock.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use std::fs::{self, File};
2+
use std::path::{Path, PathBuf};
3+
4+
#[derive(thiserror::Error, Debug)]
5+
pub enum Error {
6+
#[error("The lock at {0:?} is already acquired by a process with PID {1}.")]
7+
InUse(PathBuf, u32),
8+
}
9+
10+
/// Only one server process can "own" a project at a single time.
11+
/// We enforce this exclusive access through the creation and deletion of this lockfile.
12+
const LOCKFILE: &'static str = ".server-lock";
13+
14+
pub struct ProjectLock<'a> {
15+
file: File,
16+
path: &'a Path,
17+
}
18+
19+
impl<'a> ProjectLock<'_> {
20+
/// Attempt to acquire a lock on the provided directory.
21+
pub fn lock(project_path: &'a Path) -> Result<Self, Error> {
22+
todo!()
23+
}
24+
}
25+
26+
impl Drop for ProjectLock<'_> {
27+
fn drop(&mut self) {
28+
// The file handle is dropped before this, so we can safely delete it.
29+
fs::remove_file(self.path);
30+
}
31+
}

src/server/method.rs

Lines changed: 0 additions & 147 deletions
This file was deleted.

src/server/method/mod.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
pub mod package;
2+
pub mod project;
3+
4+
use serde::de::DeserializeOwned;
5+
use serde::{Deserialize, Serialize};
6+
7+
use self::package::PackageMethod;
8+
use self::project::ProjectMethod;
9+
use super::Error;
10+
11+
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
12+
pub enum Method {
13+
Exit,
14+
Project(ProjectMethod),
15+
Package(PackageMethod),
16+
}
17+
18+
/// Method namespace registration.
19+
impl Method {
20+
pub fn from_value(method: &str, value: serde_json::Value) -> Result<Self, Error> {
21+
let mut split = method.split('/');
22+
let (namespace, name) = (
23+
split
24+
.next()
25+
.ok_or_else(|| Error::InvalidMethod(method.into()))?,
26+
split
27+
.next()
28+
.ok_or_else(|| Error::InvalidMethod(method.into()))?,
29+
);
30+
31+
// Route namespaces to the appropriate enum variants for construction.
32+
Ok(match namespace {
33+
"exit" => Self::Exit,
34+
"project" => Self::Project(ProjectMethod::from_value(name, value)?),
35+
"package" => Self::Package(PackageMethod::from_value(name, value)?),
36+
x => Err(Error::InvalidMethod(x.into()))?,
37+
})
38+
}
39+
}
40+
41+
pub fn parse_value<T: DeserializeOwned>(value: serde_json::Value) -> Result<T, serde_json::Error> {
42+
serde_json::from_value(value)
43+
}

src/server/method/package.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
use super::Error;
4+
5+
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
6+
pub enum PackageMethod {
7+
/// Get metadata about this package.
8+
GetMetadata,
9+
/// Determine if the package exists within the cache.
10+
IsCached,
11+
}
12+
13+
impl PackageMethod {
14+
pub fn from_value(method: &str, value: serde_json::Value) -> Result<Self, Error> {
15+
Ok(match method {
16+
"get_metadata" => Self::GetMetadata,
17+
"is_cached" => Self::IsCached,
18+
x => Err(Error::InvalidMethod(x.into()))?,
19+
})
20+
}
21+
}

src/server/method/project.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::path::PathBuf;
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
use crate::ts::package_reference::PackageReference;
6+
7+
use super::Error;
8+
9+
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
10+
pub enum ProjectMethod {
11+
/// Set the project directory context. This locks the project, if it exists.
12+
SetContext(SetContext),
13+
/// Release the project directory context. This unlocks the project, if a lock exists.
14+
ReleaseContext,
15+
/// Get project metadata.
16+
GetMetadata,
17+
/// Add one or more packages to the project.
18+
AddPackages(AddPackages),
19+
/// Remove one or more packages from the project.
20+
RemovePackages(RemovePackages),
21+
/// Get a list of currently installed packages.
22+
GetPackages,
23+
/// Determine if the current context is a valid project.
24+
IsValid,
25+
}
26+
27+
impl ProjectMethod {
28+
pub fn from_value(method: &str, value: serde_json::Value) -> Result<Self, Error> {
29+
Ok(match method {
30+
"set_context" => Self::SetContext(super::parse_value(value)?),
31+
"release_context" => Self::ReleaseContext,
32+
"get_metadata" => Self::GetMetadata,
33+
"add_packages" => Self::AddPackages(super::parse_value(value)?),
34+
"remove_packages" => Self::RemovePackages(super::parse_value(value)?),
35+
"get_packages" => Self::GetPackages,
36+
"is_valid" => Self::IsValid,
37+
x => Err(Error::InvalidMethod(x.into()))?,
38+
})
39+
}
40+
}
41+
42+
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
43+
pub struct SetContext {
44+
path: PathBuf,
45+
}
46+
47+
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
48+
pub struct AddPackages {
49+
packages: Vec<PackageReference>,
50+
}
51+
52+
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
53+
pub struct RemovePackages {
54+
packages: Vec<PackageReference>,
55+
}

src/server/mod.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,90 @@
1+
use std::io::Write;
2+
use std::sync::mpsc::{self, Receiver, Sender};
3+
use std::sync::RwLock;
4+
use std::{io, thread};
5+
6+
use self::proto::{Message, Request, Response};
7+
18
mod method;
29
mod proto;
10+
mod route;
11+
12+
trait ToJson {
13+
fn to_json(&self) -> Result<String, serde_json::Error>;
14+
}
15+
16+
/// This error type exists to wrap library errors into a single easy-to-use package.
17+
#[derive(thiserror::Error, Debug)]
18+
#[repr(isize)]
19+
pub enum Error {
20+
/// A partial implementation of the error variants described by the JRPC spec.
21+
#[error("Failed to serialize JSON: {0:?}")]
22+
InvalidJson(#[from] serde_json::Error) = -32700,
23+
24+
#[error("The method {0} is not valid.")]
25+
InvalidMethod(String) = -32601,
26+
27+
#[error("Recieved invalid params for method {0}: {1}")]
28+
InvalidParams(String, String) = -32602,
29+
30+
/// Wrapper error types and codes.
31+
#[error("${0:?}")]
32+
ProjectError(String) = 1000,
33+
34+
#[error("{0:?}")]
35+
PackageError(String) = 2000,
36+
}
37+
38+
impl Error {
39+
pub fn discriminant(&self) -> isize {
40+
// SAFETY: `Self` is `repr(isize)` with layout `repr(C)`, with each variant having an isize
41+
// as its first field, so we can access this value without a pointer offset.
42+
unsafe { *<*const _>::from(self).cast::<isize>() }
43+
}
44+
}
45+
46+
impl ToJson for Result<Response, Error> {
47+
fn to_json(&self) -> Result<String, serde_json::Error> {
48+
todo!()
49+
}
50+
}
351

452
/// The daemon's entrypoint. This is a psuedo event loop which does the following in step:
553
/// 1. Read JSON-RPC input(s) from stdin.
654
/// 2. Route each input.
755
/// 3. Serialize the output and write to stdout.
8-
pub async fn start() {}
56+
async fn start() {
57+
let stdin = io::stdin();
58+
let mut line = String::new();
59+
let (tx, rx) = mpsc::channel::<Result<Response, Error>>();
60+
61+
let cancel = RwLock::new(false);
62+
63+
// Responses are published through the tx send channel.
64+
thread::spawn(move || respond_msg(rx, cancel));
65+
66+
loop {
67+
// Block the main thread until we have an input line available to be read.
68+
// This is ok because, in theory, tasks will be processed on background threads.
69+
if let Err(e) = stdin.read_line(&mut line) {
70+
panic!("")
71+
}
72+
let res = route(&line, tx.clone()).await;
73+
res.to_json().unwrap();
74+
}
75+
}
76+
77+
fn respond_msg(rx: Receiver<Result<Response, Error>>, cancel: RwLock<bool>) {
78+
let mut stdout = io::stdout();
79+
while let Ok(res) = rx.recv() {
80+
let msg = res.map(|x| serde_json::to_string(&x).unwrap());
81+
stdout.write_all(msg.unwrap().as_bytes());
82+
stdout.write_all("\n".as_bytes());
83+
}
84+
}
85+
86+
/// Route and execute the request, returning the result.
87+
async fn route(line: &str, tx: Sender<Result<Response, Error>>) -> Result<Response, Error> {
88+
let req = Message::from_json(line);
89+
todo!()
90+
}

0 commit comments

Comments
 (0)