mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-07-16 08:55:04 +00:00
Get rid of all transport types and settle on Protobuf (#25)
* Get rid of all transport types and settle on Protobuf hope i don't regret this * Update Cargo.toml * Update agent.py
This commit is contained in:
parent
643a47953e
commit
0a6e975ca5
38 changed files with 1484 additions and 685 deletions
|
@ -1,5 +1,7 @@
|
|||
use clap::{Args, Parser, Subcommand};
|
||||
use djls_ipc::{PythonProcess, Transport};
|
||||
use djls_ipc::v1::*;
|
||||
use djls_ipc::{ProcessError, PythonProcess, TransportError};
|
||||
use std::ffi::OsStr;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
|
@ -41,8 +43,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
match cli.command {
|
||||
Commands::Serve(opts) => {
|
||||
let python =
|
||||
PythonProcess::new("djls.lsp", Transport::Json, opts.health_check_interval())?;
|
||||
println!("Starting LSP server...");
|
||||
let python = PythonProcess::new::<Vec<&OsStr>, &OsStr>(
|
||||
"djls.agent",
|
||||
None,
|
||||
opts.health_check_interval(),
|
||||
)?;
|
||||
println!("LSP server started, beginning to serve...");
|
||||
djls_server::serve(python).await?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse};
|
||||
use serde::Deserialize;
|
||||
use djls_ipc::v1::*;
|
||||
use djls_ipc::{ProcessError, PythonProcess};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -20,23 +20,6 @@ impl fmt::Display for App {
|
|||
#[derive(Debug, Default)]
|
||||
pub struct Apps(Vec<App>);
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct InstalledAppsCheck {
|
||||
has_app: bool,
|
||||
}
|
||||
|
||||
impl TryFrom<JsonResponse> for InstalledAppsCheck {
|
||||
type Error = TransportError;
|
||||
|
||||
fn try_from(response: JsonResponse) -> Result<Self, Self::Error> {
|
||||
response
|
||||
.data()
|
||||
.clone()
|
||||
.ok_or_else(|| TransportError::Process("No data in response".to_string()))
|
||||
.and_then(|data| serde_json::from_value(data).map_err(TransportError::Json))
|
||||
}
|
||||
}
|
||||
|
||||
impl Apps {
|
||||
pub fn from_strings(apps: Vec<String>) -> Self {
|
||||
Self(apps.into_iter().map(App).collect())
|
||||
|
@ -54,18 +37,21 @@ impl Apps {
|
|||
self.apps().iter()
|
||||
}
|
||||
|
||||
pub fn check_installed(python: &mut PythonProcess, app: &str) -> Result<bool, TransportError> {
|
||||
let message = TransportMessage::Json("installed_apps_check".to_string());
|
||||
let response = python.send(message, Some(vec![app.to_string()]))?;
|
||||
match response {
|
||||
TransportResponse::Json(json_str) => {
|
||||
let json_response: JsonResponse = serde_json::from_str(&json_str)?;
|
||||
let result = InstalledAppsCheck::try_from(json_response)?;
|
||||
Ok(result.has_app)
|
||||
}
|
||||
_ => Err(TransportError::Process(
|
||||
"Unexpected response type".to_string(),
|
||||
pub fn check_installed(python: &mut PythonProcess, app: &str) -> Result<bool, ProcessError> {
|
||||
let request = messages::Request {
|
||||
command: Some(messages::request::Command::CheckAppInstalled(
|
||||
check::AppInstalledRequest {
|
||||
app_name: app.to_string(),
|
||||
},
|
||||
)),
|
||||
};
|
||||
|
||||
let response = python.send(request).map_err(ProcessError::Transport)?;
|
||||
|
||||
match response.result {
|
||||
Some(messages::response::Result::CheckAppInstalled(response)) => Ok(response.passed),
|
||||
Some(messages::response::Result::Error(e)) => Err(ProcessError::Health(e.message)),
|
||||
_ => Err(ProcessError::Response),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,63 +1,26 @@
|
|||
use crate::apps::Apps;
|
||||
use crate::gis::{check_gis_setup, GISError};
|
||||
use crate::templates::TemplateTags;
|
||||
use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse};
|
||||
use djls_ipc::v1::*;
|
||||
use djls_ipc::{ProcessError, PythonProcess, TransportError};
|
||||
use djls_python::{ImportCheck, Python};
|
||||
use serde::Deserialize;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DjangoProject {
|
||||
py: Python,
|
||||
python: PythonProcess,
|
||||
settings_module: String,
|
||||
installed_apps: Apps,
|
||||
templatetags: TemplateTags,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct DjangoSetup {
|
||||
installed_apps: Vec<String>,
|
||||
templatetags: TemplateTags,
|
||||
}
|
||||
|
||||
impl DjangoSetup {
|
||||
pub fn setup(python: &mut PythonProcess) -> Result<JsonResponse, ProjectError> {
|
||||
let message = TransportMessage::Json("django_setup".to_string());
|
||||
let response = python.send(message, None)?;
|
||||
match response {
|
||||
TransportResponse::Json(json_str) => {
|
||||
let json_response: JsonResponse = serde_json::from_str(&json_str)?;
|
||||
Ok(json_response)
|
||||
}
|
||||
_ => Err(ProjectError::Transport(TransportError::Process(
|
||||
"Unexpected response type".to_string(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
version: String,
|
||||
}
|
||||
|
||||
impl DjangoProject {
|
||||
fn new(
|
||||
py: Python,
|
||||
python: PythonProcess,
|
||||
settings_module: String,
|
||||
installed_apps: Apps,
|
||||
templatetags: TemplateTags,
|
||||
) -> Self {
|
||||
fn new(py: Python, python: PythonProcess, version: String) -> Self {
|
||||
Self {
|
||||
py,
|
||||
python,
|
||||
settings_module,
|
||||
installed_apps,
|
||||
templatetags,
|
||||
version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup(mut python: PythonProcess) -> Result<Self, ProjectError> {
|
||||
let settings_module =
|
||||
std::env::var("DJANGO_SETTINGS_MODULE").expect("DJANGO_SETTINGS_MODULE must be set");
|
||||
|
||||
let py = Python::setup(&mut python)?;
|
||||
|
||||
let has_django = ImportCheck::check(&mut python, Some(vec!["django".to_string()]))?;
|
||||
|
@ -74,45 +37,52 @@ impl DjangoProject {
|
|||
return Ok(Self {
|
||||
py,
|
||||
python,
|
||||
settings_module,
|
||||
installed_apps: Apps::default(),
|
||||
templatetags: TemplateTags::default(),
|
||||
version: String::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let response = DjangoSetup::setup(&mut python)?;
|
||||
let setup: DjangoSetup = response
|
||||
.data()
|
||||
.clone()
|
||||
.ok_or_else(|| TransportError::Process("No data in response".to_string()))
|
||||
.and_then(|data| serde_json::from_value(data).map_err(TransportError::Json))?;
|
||||
let request = messages::Request {
|
||||
command: Some(messages::request::Command::DjangoGetProjectInfo(
|
||||
django::GetProjectInfoRequest {},
|
||||
)),
|
||||
};
|
||||
|
||||
Ok(Self::new(
|
||||
let response = python
|
||||
.send(request)
|
||||
.map_err(|e| ProjectError::Transport(e))?;
|
||||
|
||||
let version = match response.result {
|
||||
Some(messages::response::Result::DjangoGetProjectInfo(response)) => {
|
||||
response.project.unwrap().version
|
||||
}
|
||||
Some(messages::response::Result::Error(e)) => {
|
||||
return Err(ProjectError::Process(ProcessError::Health(e.message)));
|
||||
}
|
||||
_ => {
|
||||
return Err(ProjectError::Process(ProcessError::Response));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
py,
|
||||
python,
|
||||
settings_module,
|
||||
Apps::from_strings(setup.installed_apps.to_vec()),
|
||||
setup.templatetags,
|
||||
))
|
||||
version,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn py(&self) -> &Python {
|
||||
&self.py
|
||||
}
|
||||
|
||||
fn settings_module(&self) -> &String {
|
||||
&self.settings_module
|
||||
fn version(&self) -> &String {
|
||||
&self.version
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DjangoProject {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "Django Project")?;
|
||||
writeln!(f, "Settings Module: {}", self.settings_module)?;
|
||||
writeln!(f, "Installed Apps:")?;
|
||||
write!(f, "{}", self.installed_apps)?;
|
||||
writeln!(f, "Template Tags:")?;
|
||||
write!(f, "{}", self.templatetags)?;
|
||||
writeln!(f, "Version: {}", self.version)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -121,22 +91,18 @@ impl fmt::Display for DjangoProject {
|
|||
pub enum ProjectError {
|
||||
#[error("Django is not installed or cannot be imported")]
|
||||
DjangoNotFound,
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("GIS error: {0}")]
|
||||
Gis(#[from] GISError),
|
||||
|
||||
#[error("JSON parsing error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Packaging(#[from] djls_python::PackagingError),
|
||||
|
||||
#[error("Process error: {0}")]
|
||||
Process(#[from] ProcessError),
|
||||
#[error(transparent)]
|
||||
Python(#[from] djls_python::PythonError),
|
||||
|
||||
#[error("Transport error: {0}")]
|
||||
Transport(#[from] TransportError),
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::apps::Apps;
|
||||
use djls_ipc::{PythonProcess, TransportError};
|
||||
use djls_ipc::{ProcessError, PythonProcess, TransportError};
|
||||
use std::process::Command;
|
||||
|
||||
pub fn check_gis_setup(python: &mut PythonProcess) -> Result<bool, GISError> {
|
||||
|
@ -17,10 +17,10 @@ pub fn check_gis_setup(python: &mut PythonProcess) -> Result<bool, GISError> {
|
|||
pub enum GISError {
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("JSON parsing error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[error("Process error: {0}")]
|
||||
Process(#[from] ProcessError),
|
||||
#[error("Transport error: {0}")]
|
||||
Transport(#[from] TransportError),
|
||||
}
|
||||
|
|
|
@ -4,15 +4,16 @@ version = "0.0.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
djls-types = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
prost = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
tempfile = "3.14.0"
|
||||
bytes = "1.9"
|
||||
prost = "0.13"
|
||||
tempfile = "3.14"
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.13"
|
||||
|
|
38
crates/djls-ipc/build.rs
Normal file
38
crates/djls-ipc/build.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
struct Version(&'static str);
|
||||
|
||||
impl Version {
|
||||
fn collect_protos(&self, proto_root: &Path) -> Vec<PathBuf> {
|
||||
fs::read_dir(proto_root.join(self.0))
|
||||
.unwrap()
|
||||
.filter_map(Result::ok)
|
||||
.filter(|entry| entry.path().extension().and_then(|s| s.to_str()) == Some("proto"))
|
||||
.map(|entry| entry.path())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
const VERSIONS: &[Version] = &[Version("v1")];
|
||||
|
||||
fn main() {
|
||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let workspace_root = manifest_dir.parent().unwrap().parent().unwrap();
|
||||
let proto_dir = workspace_root.join("proto");
|
||||
|
||||
let mut protos = Vec::new();
|
||||
for version in VERSIONS {
|
||||
protos.extend(version.collect_protos(&proto_dir));
|
||||
}
|
||||
|
||||
prost_build::Config::new()
|
||||
.compile_protos(
|
||||
&protos
|
||||
.iter()
|
||||
.map(|p| p.to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
&[proto_dir],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
mod process;
|
||||
mod proto;
|
||||
mod transport;
|
||||
|
||||
pub use process::ProcessError;
|
||||
pub use process::PythonProcess;
|
||||
pub use transport::parse_json_response;
|
||||
pub use transport::parse_raw_response;
|
||||
pub use transport::JsonResponse;
|
||||
pub use proto::v1;
|
||||
pub use transport::Transport;
|
||||
pub use transport::TransportError;
|
||||
pub use transport::TransportMessage;
|
||||
pub use transport::TransportResponse;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::transport::{
|
||||
Transport, TransportError, TransportMessage, TransportProtocol, TransportResponse,
|
||||
};
|
||||
use crate::proto::v1::*;
|
||||
use crate::transport::{Transport, TransportError};
|
||||
use std::ffi::OsStr;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
@ -9,75 +9,130 @@ use tokio::time;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct PythonProcess {
|
||||
transport: Arc<Mutex<Box<dyn TransportProtocol>>>,
|
||||
transport: Arc<Mutex<Transport>>,
|
||||
_child: Child,
|
||||
healthy: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl PythonProcess {
|
||||
pub fn new(
|
||||
pub fn new<I, S>(
|
||||
module: &str,
|
||||
transport: Transport,
|
||||
args: Option<I>,
|
||||
health_check_interval: Option<Duration>,
|
||||
) -> Result<Self, TransportError> {
|
||||
let mut child = Command::new("python")
|
||||
.arg("-m")
|
||||
.arg(module)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
) -> Result<Self, ProcessError>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
let mut command = Command::new("python");
|
||||
command.arg("-m").arg(module);
|
||||
|
||||
if let Some(args) = args {
|
||||
command.args(args);
|
||||
}
|
||||
|
||||
command.stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||
|
||||
let mut child = command.spawn().map_err(TransportError::Io)?;
|
||||
|
||||
let stdin = child.stdin.take().unwrap();
|
||||
let stdout = child.stdout.take().unwrap();
|
||||
|
||||
let transport = Transport::new(stdin, stdout)?;
|
||||
|
||||
let process = Self {
|
||||
transport: Arc::new(Mutex::new(transport.create(stdin, stdout)?)),
|
||||
transport: Arc::new(Mutex::new(transport)),
|
||||
_child: child,
|
||||
healthy: Arc::new(AtomicBool::new(true)),
|
||||
};
|
||||
|
||||
if let Some(interval) = health_check_interval {
|
||||
process.start_health_check_task(interval)?;
|
||||
let transport = process.transport.clone();
|
||||
let healthy = process.healthy.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut interval = time::interval(interval);
|
||||
loop {
|
||||
interval.tick().await;
|
||||
let _ = PythonProcess::check_health(transport.clone(), healthy.clone()).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(process)
|
||||
}
|
||||
|
||||
fn start_health_check_task(&self, interval: Duration) -> Result<(), TransportError> {
|
||||
let healthy = self.healthy.clone();
|
||||
let transport = self.transport.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut interval = time::interval(interval);
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
if let Ok(mut transport) = transport.lock() {
|
||||
match transport.health_check() {
|
||||
Ok(()) => {
|
||||
healthy.store(true, Ordering::SeqCst);
|
||||
}
|
||||
Err(_) => {
|
||||
healthy.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_healthy(&self) -> bool {
|
||||
self.healthy.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn send(
|
||||
&mut self,
|
||||
message: TransportMessage,
|
||||
args: Option<Vec<String>>,
|
||||
) -> Result<TransportResponse, TransportError> {
|
||||
request: messages::Request,
|
||||
) -> Result<messages::Response, TransportError> {
|
||||
let mut transport = self.transport.lock().unwrap();
|
||||
transport.send(message, args)
|
||||
transport.send(request)
|
||||
}
|
||||
|
||||
async fn check_health(
|
||||
transport: Arc<Mutex<Transport>>,
|
||||
healthy: Arc<AtomicBool>,
|
||||
) -> Result<(), ProcessError> {
|
||||
let request = messages::Request {
|
||||
command: Some(messages::request::Command::CheckHealth(
|
||||
check::HealthRequest {},
|
||||
)),
|
||||
};
|
||||
|
||||
let response = tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut transport = transport.lock().unwrap();
|
||||
transport.send(request)
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| ProcessError::Timeout(5))?
|
||||
.map_err(TransportError::Task)?
|
||||
.map_err(ProcessError::Transport)?;
|
||||
|
||||
let result = match response.result {
|
||||
Some(messages::response::Result::CheckHealth(health)) => {
|
||||
if !health.passed {
|
||||
let error_msg = health.error.unwrap_or_else(|| "Unknown error".to_string());
|
||||
Err(ProcessError::Health(error_msg))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Some(messages::response::Result::Error(e)) => Err(ProcessError::Health(e.message)),
|
||||
_ => Err(ProcessError::Response),
|
||||
};
|
||||
|
||||
healthy.store(result.is_ok(), Ordering::SeqCst);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PythonProcess {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(()) = self._child.kill() {
|
||||
let _ = self._child.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ProcessError {
|
||||
#[error("Health check failed: {0}")]
|
||||
Health(String),
|
||||
#[error("Operation timed out after {0} seconds")]
|
||||
Timeout(u64),
|
||||
#[error("Unexpected response type")]
|
||||
Response,
|
||||
#[error("Failed to acquire lock: {0}")]
|
||||
Lock(String),
|
||||
#[error("Process not ready: {0}")]
|
||||
Ready(String),
|
||||
#[error("Transport error: {0}")]
|
||||
Transport(#[from] TransportError),
|
||||
}
|
||||
|
|
17
crates/djls-ipc/src/proto.rs
Normal file
17
crates/djls-ipc/src/proto.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
pub mod v1 {
|
||||
pub mod messages {
|
||||
include!(concat!(env!("OUT_DIR"), "/djls.v1.messages.rs"));
|
||||
}
|
||||
|
||||
pub mod check {
|
||||
include!(concat!(env!("OUT_DIR"), "/djls.v1.check.rs"));
|
||||
}
|
||||
|
||||
pub mod django {
|
||||
include!(concat!(env!("OUT_DIR"), "/djls.v1.django.rs"));
|
||||
}
|
||||
|
||||
pub mod python {
|
||||
include!(concat!(env!("OUT_DIR"), "/djls.v1.python.rs"));
|
||||
}
|
||||
}
|
|
@ -1,320 +1,59 @@
|
|||
use djls_types::proto::*;
|
||||
use crate::process::ProcessError;
|
||||
use crate::proto::v1::*;
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::fmt::Debug;
|
||||
use std::io::Read;
|
||||
use std::io::{BufRead, BufReader, BufWriter, Write};
|
||||
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
|
||||
use std::process::{ChildStdin, ChildStdout};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TransportError {
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("JSON error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error("Process error: {0}")]
|
||||
Process(String),
|
||||
}
|
||||
|
||||
pub enum Transport {
|
||||
Raw,
|
||||
Json,
|
||||
Protobuf,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Transport {
|
||||
reader: Arc<Mutex<BufReader<ChildStdout>>>,
|
||||
writer: Arc<Mutex<BufWriter<ChildStdin>>>,
|
||||
}
|
||||
|
||||
impl Transport {
|
||||
pub fn create(
|
||||
&self,
|
||||
mut stdin: ChildStdin,
|
||||
mut stdout: ChildStdout,
|
||||
) -> Result<Box<dyn TransportProtocol>, TransportError> {
|
||||
let transport_type = match self {
|
||||
Transport::Raw => "raw",
|
||||
Transport::Json => "json",
|
||||
Transport::Protobuf => "protobuf",
|
||||
};
|
||||
|
||||
writeln!(stdin, "{}", transport_type).map_err(TransportError::Io)?;
|
||||
pub fn new(mut stdin: ChildStdin, mut stdout: ChildStdout) -> Result<Self, ProcessError> {
|
||||
stdin.flush().map_err(TransportError::Io)?;
|
||||
|
||||
let mut ready_line = String::new();
|
||||
BufReader::new(&mut stdout)
|
||||
.read_line(&mut ready_line)
|
||||
.map_err(TransportError::Io)?;
|
||||
|
||||
if ready_line.trim() != "ready" {
|
||||
return Err(TransportError::Process(
|
||||
"Python process not ready".to_string(),
|
||||
));
|
||||
return Err(ProcessError::Ready("Python process not ready".to_string()));
|
||||
}
|
||||
|
||||
match self {
|
||||
Transport::Raw => Ok(Box::new(RawTransport::new(stdin, stdout)?)),
|
||||
Transport::Json => Ok(Box::new(JsonTransport::new(stdin, stdout)?)),
|
||||
Transport::Protobuf => Ok(Box::new(ProtobufTransport::new(stdin, stdout)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TransportMessage {
|
||||
Raw(String),
|
||||
Json(String),
|
||||
Protobuf(ToAgent),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TransportResponse {
|
||||
Raw(String),
|
||||
Json(String),
|
||||
Protobuf(FromAgent),
|
||||
}
|
||||
|
||||
pub trait TransportProtocol: Debug + Send {
|
||||
fn new(stdin: ChildStdin, stdout: ChildStdout) -> Result<Self, TransportError>
|
||||
where
|
||||
Self: Sized;
|
||||
fn health_check(&mut self) -> Result<(), TransportError>;
|
||||
fn clone_box(&self) -> Box<dyn TransportProtocol>;
|
||||
fn send_impl(
|
||||
&mut self,
|
||||
message: TransportMessage,
|
||||
args: Option<Vec<String>>,
|
||||
) -> Result<TransportResponse, TransportError>;
|
||||
|
||||
fn send(
|
||||
&mut self,
|
||||
message: TransportMessage,
|
||||
args: Option<Vec<String>>,
|
||||
) -> Result<TransportResponse, TransportError> {
|
||||
self.health_check()?;
|
||||
self.send_impl(message, args)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn TransportProtocol> {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone_box()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RawTransport {
|
||||
reader: Arc<Mutex<BufReader<ChildStdout>>>,
|
||||
writer: Arc<Mutex<BufWriter<ChildStdin>>>,
|
||||
}
|
||||
|
||||
impl TransportProtocol for RawTransport {
|
||||
fn new(stdin: ChildStdin, stdout: ChildStdout) -> Result<Self, TransportError> {
|
||||
Ok(Self {
|
||||
reader: Arc::new(Mutex::new(BufReader::new(stdout))),
|
||||
writer: Arc::new(Mutex::new(BufWriter::new(stdin))),
|
||||
})
|
||||
}
|
||||
|
||||
fn health_check(&mut self) -> Result<(), TransportError> {
|
||||
self.send_impl(TransportMessage::Raw("health".to_string()), None)
|
||||
.and_then(|response| match response {
|
||||
TransportResponse::Raw(s) if s == "ok" => Ok(()),
|
||||
TransportResponse::Raw(other) => Err(TransportError::Process(format!(
|
||||
"Health check failed: {}",
|
||||
other
|
||||
))),
|
||||
_ => Err(TransportError::Process(
|
||||
"Unexpected response type".to_string(),
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn TransportProtocol> {
|
||||
Box::new(RawTransport {
|
||||
reader: self.reader.clone(),
|
||||
writer: self.writer.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn send_impl(
|
||||
pub fn send(
|
||||
&mut self,
|
||||
message: TransportMessage,
|
||||
args: Option<Vec<String>>,
|
||||
) -> Result<TransportResponse, TransportError> {
|
||||
let mut writer = self.writer.lock().unwrap();
|
||||
|
||||
match message {
|
||||
TransportMessage::Raw(msg) => {
|
||||
if let Some(args) = args {
|
||||
writeln!(writer, "{} {}", msg, args.join(" ")).map_err(TransportError::Io)?;
|
||||
} else {
|
||||
writeln!(writer, "{}", msg).map_err(TransportError::Io)?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(TransportError::Process(
|
||||
"Raw transport only accepts raw messages".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
message: messages::Request,
|
||||
) -> Result<messages::Response, TransportError> {
|
||||
let buf = message.encode_to_vec();
|
||||
|
||||
let mut writer = self.writer.lock().map_err(|_| {
|
||||
TransportError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Failed to acquire writer lock",
|
||||
))
|
||||
})?;
|
||||
writer
|
||||
.write_all(&(buf.len() as u32).to_be_bytes())
|
||||
.map_err(TransportError::Io)?;
|
||||
writer.write_all(&buf).map_err(TransportError::Io)?;
|
||||
writer.flush().map_err(TransportError::Io)?;
|
||||
|
||||
let mut reader = self.reader.lock().unwrap();
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line).map_err(TransportError::Io)?;
|
||||
Ok(TransportResponse::Raw(line.trim().to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct JsonCommand {
|
||||
command: String,
|
||||
args: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct JsonResponse {
|
||||
status: String,
|
||||
data: Option<Value>,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
impl JsonResponse {
|
||||
pub fn data(&self) -> &Option<Value> {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JsonTransport {
|
||||
reader: Arc<Mutex<BufReader<ChildStdout>>>,
|
||||
writer: Arc<Mutex<BufWriter<ChildStdin>>>,
|
||||
}
|
||||
|
||||
impl TransportProtocol for JsonTransport {
|
||||
fn new(stdin: ChildStdin, stdout: ChildStdout) -> Result<Self, TransportError> {
|
||||
Ok(Self {
|
||||
reader: Arc::new(Mutex::new(BufReader::new(stdout))),
|
||||
writer: Arc::new(Mutex::new(BufWriter::new(stdin))),
|
||||
})
|
||||
}
|
||||
|
||||
fn health_check(&mut self) -> Result<(), TransportError> {
|
||||
self.send_impl(TransportMessage::Json("health".to_string()), None)
|
||||
.and_then(|response| match response {
|
||||
TransportResponse::Json(json) => {
|
||||
let resp: JsonResponse = serde_json::from_str(&json)?;
|
||||
match resp.status.as_str() {
|
||||
"ok" => Ok(()),
|
||||
_ => Err(TransportError::Process(
|
||||
resp.error.unwrap_or_else(|| "Unknown error".to_string()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => Err(TransportError::Process(
|
||||
"Unexpected response type".to_string(),
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn TransportProtocol> {
|
||||
Box::new(JsonTransport {
|
||||
reader: self.reader.clone(),
|
||||
writer: self.writer.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn send_impl(
|
||||
&mut self,
|
||||
message: TransportMessage,
|
||||
args: Option<Vec<String>>,
|
||||
) -> Result<TransportResponse, TransportError> {
|
||||
let mut writer = self.writer.lock().unwrap();
|
||||
|
||||
match message {
|
||||
TransportMessage::Json(msg) => {
|
||||
let command = JsonCommand { command: msg, args };
|
||||
serde_json::to_writer(&mut *writer, &command)?;
|
||||
writeln!(writer).map_err(TransportError::Io)?;
|
||||
}
|
||||
_ => {
|
||||
return Err(TransportError::Process(
|
||||
"JSON transport only accepts JSON messages".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
writer.flush().map_err(TransportError::Io)?;
|
||||
|
||||
let mut reader = self.reader.lock().unwrap();
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line).map_err(TransportError::Io)?;
|
||||
Ok(TransportResponse::Json(line.trim().to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProtobufTransport {
|
||||
reader: Arc<Mutex<BufReader<ChildStdout>>>,
|
||||
writer: Arc<Mutex<BufWriter<ChildStdin>>>,
|
||||
}
|
||||
|
||||
impl TransportProtocol for ProtobufTransport {
|
||||
fn new(stdin: ChildStdin, stdout: ChildStdout) -> Result<Self, TransportError> {
|
||||
Ok(Self {
|
||||
reader: Arc::new(Mutex::new(BufReader::new(stdout))),
|
||||
writer: Arc::new(Mutex::new(BufWriter::new(stdin))),
|
||||
})
|
||||
}
|
||||
|
||||
fn health_check(&mut self) -> Result<(), TransportError> {
|
||||
let request = ToAgent {
|
||||
command: Some(to_agent::Command::HealthCheck(HealthCheck {})),
|
||||
};
|
||||
|
||||
match self.send_impl(TransportMessage::Protobuf(request), None)? {
|
||||
TransportResponse::Protobuf(FromAgent {
|
||||
message: Some(from_agent::Message::Error(e)),
|
||||
}) => Err(TransportError::Process(e.message)),
|
||||
TransportResponse::Protobuf(FromAgent {
|
||||
message: Some(from_agent::Message::HealthCheck(_)),
|
||||
}) => Ok(()),
|
||||
_ => Err(TransportError::Process("Unexpected response".to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn TransportProtocol> {
|
||||
Box::new(ProtobufTransport {
|
||||
reader: self.reader.clone(),
|
||||
writer: self.writer.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn send_impl(
|
||||
&mut self,
|
||||
message: TransportMessage,
|
||||
_args: Option<Vec<String>>,
|
||||
) -> Result<TransportResponse, TransportError> {
|
||||
let mut writer = self.writer.lock().unwrap();
|
||||
|
||||
match message {
|
||||
TransportMessage::Protobuf(msg) => {
|
||||
let buf = msg.encode_to_vec();
|
||||
writer
|
||||
.write_all(&(buf.len() as u32).to_be_bytes())
|
||||
.map_err(TransportError::Io)?;
|
||||
writer.write_all(&buf).map_err(TransportError::Io)?;
|
||||
}
|
||||
_ => {
|
||||
return Err(TransportError::Process(
|
||||
"Protobuf transport only accepts protobuf messages".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
writer.flush().map_err(TransportError::Io)?;
|
||||
|
||||
let mut reader = self.reader.lock().unwrap();
|
||||
let mut reader = self.reader.lock().map_err(|_| {
|
||||
TransportError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Failed to acquire reader lock",
|
||||
))
|
||||
})?;
|
||||
let mut length_bytes = [0u8; 4];
|
||||
reader
|
||||
.read_exact(&mut length_bytes)
|
||||
|
@ -326,17 +65,17 @@ impl TransportProtocol for ProtobufTransport {
|
|||
.read_exact(&mut message_bytes)
|
||||
.map_err(TransportError::Io)?;
|
||||
|
||||
let response = FromAgent::decode(message_bytes.as_slice())
|
||||
.map_err(|e| TransportError::Process(e.to_string()))?;
|
||||
|
||||
Ok(TransportResponse::Protobuf(response))
|
||||
messages::Response::decode(message_bytes.as_slice())
|
||||
.map_err(|e| TransportError::Decode(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_raw_response(response: String) -> Result<String, TransportError> {
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn parse_json_response(response: String) -> Result<JsonResponse, TransportError> {
|
||||
serde_json::from_str(&response).map_err(TransportError::Json)
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum TransportError {
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Task error: {0}")]
|
||||
Task(#[from] tokio::task::JoinError),
|
||||
#[error("Failed to decode message: {0}")]
|
||||
Decode(String),
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse};
|
||||
use djls_ipc::v1::*;
|
||||
use djls_ipc::{ProcessError, PythonProcess, TransportError};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
@ -6,15 +7,25 @@ use std::path::PathBuf;
|
|||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Package {
|
||||
name: String,
|
||||
version: String,
|
||||
location: Option<PathBuf>,
|
||||
dist_name: String,
|
||||
dist_version: String,
|
||||
dist_location: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl From<python::Package> for Package {
|
||||
fn from(p: python::Package) -> Self {
|
||||
Package {
|
||||
dist_name: p.dist_name,
|
||||
dist_version: p.dist_version,
|
||||
dist_location: p.dist_location.map(PathBuf::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Package {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} {}", self.name, self.version)?;
|
||||
if let Some(location) = &self.location {
|
||||
write!(f, "{} {}", self.dist_name, self.dist_version)?;
|
||||
if let Some(location) = &self.dist_location {
|
||||
write!(f, " ({})", location.display())?;
|
||||
}
|
||||
Ok(())
|
||||
|
@ -30,6 +41,12 @@ impl Packages {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<HashMap<String, python::Package>> for Packages {
|
||||
fn from(packages: HashMap<String, python::Package>) -> Self {
|
||||
Packages(packages.into_iter().map(|(k, v)| (k, v.into())).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, Package)> for Packages {
|
||||
fn from_iter<T: IntoIterator<Item = (String, Package)>>(iter: T) -> Self {
|
||||
Self(HashMap::from_iter(iter))
|
||||
|
@ -39,7 +56,7 @@ impl FromIterator<(String, Package)> for Packages {
|
|||
impl fmt::Display for Packages {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut packages: Vec<_> = self.packages();
|
||||
packages.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
packages.sort_by(|a, b| a.dist_name.cmp(&b.dist_name));
|
||||
|
||||
if packages.is_empty() {
|
||||
writeln!(f, " (no packages installed)")?;
|
||||
|
@ -57,18 +74,6 @@ pub struct ImportCheck {
|
|||
can_import: bool,
|
||||
}
|
||||
|
||||
impl TryFrom<JsonResponse> for ImportCheck {
|
||||
type Error = TransportError;
|
||||
|
||||
fn try_from(response: JsonResponse) -> Result<Self, Self::Error> {
|
||||
response
|
||||
.data()
|
||||
.clone()
|
||||
.ok_or_else(|| TransportError::Process("No data in response".to_string()))
|
||||
.and_then(|data| serde_json::from_value(data).map_err(TransportError::Json))
|
||||
}
|
||||
}
|
||||
|
||||
impl ImportCheck {
|
||||
pub fn can_import(&self) -> bool {
|
||||
self.can_import
|
||||
|
@ -76,19 +81,24 @@ impl ImportCheck {
|
|||
|
||||
pub fn check(
|
||||
python: &mut PythonProcess,
|
||||
modules: Option<Vec<String>>,
|
||||
_modules: Option<Vec<String>>,
|
||||
) -> Result<bool, PackagingError> {
|
||||
let message = TransportMessage::Json("has_import".to_string());
|
||||
let response = python.send(message, modules)?;
|
||||
match response {
|
||||
TransportResponse::Json(json_str) => {
|
||||
let json_response: JsonResponse = serde_json::from_str(&json_str)?;
|
||||
let check = Self::try_from(json_response)?;
|
||||
Ok(check.can_import)
|
||||
let request = messages::Request {
|
||||
command: Some(messages::request::Command::CheckDjangoAvailable(
|
||||
check::DjangoAvailableRequest {},
|
||||
)),
|
||||
};
|
||||
|
||||
let response = python
|
||||
.send(request)
|
||||
.map_err(|e| PackagingError::Transport(e))?;
|
||||
|
||||
match response.result {
|
||||
Some(messages::response::Result::CheckDjangoAvailable(response)) => Ok(response.passed),
|
||||
Some(messages::response::Result::Error(e)) => {
|
||||
Err(PackagingError::Process(ProcessError::Health(e.message)))
|
||||
}
|
||||
_ => Err(PackagingError::Transport(TransportError::Process(
|
||||
"Unexpected response type".to_string(),
|
||||
))),
|
||||
_ => Err(PackagingError::Process(ProcessError::Response)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +113,8 @@ pub enum PackagingError {
|
|||
|
||||
#[error("Transport error: {0}")]
|
||||
Transport(#[from] TransportError),
|
||||
#[error("Process error: {0}")]
|
||||
Process(#[from] ProcessError),
|
||||
|
||||
#[error("UTF-8 conversion error: {0}")]
|
||||
Utf8(#[from] std::string::FromUtf8Error),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::packaging::{Packages, PackagingError};
|
||||
use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse};
|
||||
use djls_ipc::v1::*;
|
||||
use djls_ipc::{ProcessError, PythonProcess, TransportError};
|
||||
use serde::Deserialize;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
@ -8,16 +9,45 @@ use std::path::PathBuf;
|
|||
pub struct VersionInfo {
|
||||
major: u8,
|
||||
minor: u8,
|
||||
patch: u8,
|
||||
suffix: Option<String>,
|
||||
micro: u8,
|
||||
releaselevel: ReleaseLevel,
|
||||
serial: Option<String>,
|
||||
}
|
||||
|
||||
impl From<python::VersionInfo> for VersionInfo {
|
||||
fn from(v: python::VersionInfo) -> Self {
|
||||
Self {
|
||||
major: v.major as u8,
|
||||
minor: v.minor as u8,
|
||||
micro: v.micro as u8,
|
||||
releaselevel: v.releaselevel().into(),
|
||||
serial: Some(v.serial.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub enum ReleaseLevel {
|
||||
Alpha,
|
||||
Beta,
|
||||
Candidate,
|
||||
Final,
|
||||
}
|
||||
|
||||
impl From<python::ReleaseLevel> for ReleaseLevel {
|
||||
fn from(level: python::ReleaseLevel) -> Self {
|
||||
match level {
|
||||
python::ReleaseLevel::Alpha => ReleaseLevel::Alpha,
|
||||
python::ReleaseLevel::Beta => ReleaseLevel::Beta,
|
||||
python::ReleaseLevel::Candidate => ReleaseLevel::Candidate,
|
||||
python::ReleaseLevel::Final => ReleaseLevel::Final,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VersionInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
|
||||
if let Some(suffix) = &self.suffix {
|
||||
write!(f, "{}", suffix)?;
|
||||
}
|
||||
write!(f, "{}.{}.{}", self.major, self.minor, self.micro)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -58,30 +88,52 @@ pub struct Python {
|
|||
packages: Packages,
|
||||
}
|
||||
|
||||
impl TryFrom<JsonResponse> for Python {
|
||||
type Error = TransportError;
|
||||
impl Python {
|
||||
pub fn setup(python: &mut PythonProcess) -> Result<Self, PythonError> {
|
||||
let request = messages::Request {
|
||||
command: Some(messages::request::Command::PythonGetEnvironment(
|
||||
python::GetEnvironmentRequest {},
|
||||
)),
|
||||
};
|
||||
|
||||
fn try_from(response: JsonResponse) -> Result<Self, Self::Error> {
|
||||
response
|
||||
.data()
|
||||
.clone()
|
||||
.ok_or_else(|| TransportError::Process("No data in response".to_string()))
|
||||
.and_then(|data| serde_json::from_value(data).map_err(TransportError::Json))
|
||||
let response = python.send(request).map_err(PythonError::Transport)?;
|
||||
|
||||
match response.result {
|
||||
Some(messages::response::Result::PythonGetEnvironment(response)) => response
|
||||
.python
|
||||
.ok_or_else(|| PythonError::Process(ProcessError::Response))
|
||||
.map(Into::into),
|
||||
Some(messages::response::Result::Error(e)) => {
|
||||
Err(PythonError::Process(ProcessError::Health(e.message)))
|
||||
}
|
||||
_ => Err(PythonError::Process(ProcessError::Response)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Python {
|
||||
pub fn setup(python: &mut PythonProcess) -> Result<Self, PythonError> {
|
||||
let message = TransportMessage::Json("python_setup".to_string());
|
||||
let response = python.send(message, None)?;
|
||||
match response {
|
||||
TransportResponse::Json(json_str) => {
|
||||
let json_response: JsonResponse = serde_json::from_str(&json_str)?;
|
||||
Ok(Self::try_from(json_response)?)
|
||||
}
|
||||
_ => Err(PythonError::Transport(TransportError::Process(
|
||||
"Unexpected response type".to_string(),
|
||||
))),
|
||||
impl From<python::Python> for Python {
|
||||
fn from(p: python::Python) -> Self {
|
||||
let sys = p.sys.unwrap();
|
||||
let sysconfig = p.sysconfig.unwrap();
|
||||
let site = p.site.unwrap();
|
||||
|
||||
Self {
|
||||
version_info: sys.version_info.unwrap_or_default().into(),
|
||||
sysconfig_paths: SysconfigPaths {
|
||||
data: PathBuf::from(sysconfig.data),
|
||||
include: PathBuf::from(sysconfig.include),
|
||||
platinclude: PathBuf::from(sysconfig.platinclude),
|
||||
platlib: PathBuf::from(sysconfig.platlib),
|
||||
platstdlib: PathBuf::from(sysconfig.platstdlib),
|
||||
purelib: PathBuf::from(sysconfig.purelib),
|
||||
scripts: PathBuf::from(sysconfig.scripts),
|
||||
stdlib: PathBuf::from(sysconfig.stdlib),
|
||||
},
|
||||
sys_prefix: PathBuf::from(sys.prefix),
|
||||
sys_base_prefix: PathBuf::from(sys.base_prefix),
|
||||
sys_executable: PathBuf::from(sys.executable),
|
||||
sys_path: sys.path.into_iter().map(PathBuf::from).collect(),
|
||||
packages: site.packages.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,25 +159,20 @@ impl fmt::Display for Python {
|
|||
pub enum PythonError {
|
||||
#[error("Python execution failed: {0}")]
|
||||
Execution(String),
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("JSON parsing error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[error("Packaging error: {0}")]
|
||||
Packaging(#[from] PackagingError),
|
||||
|
||||
#[error("Integer parsing error: {0}")]
|
||||
Parse(#[from] std::num::ParseIntError),
|
||||
|
||||
#[error("Process error: {0}")]
|
||||
Process(#[from] ProcessError),
|
||||
#[error("Failed to locate Python executable: {0}")]
|
||||
PythonNotFound(#[from] which::Error),
|
||||
|
||||
#[error("Transport error: {0}")]
|
||||
Transport(#[from] TransportError),
|
||||
|
||||
#[error("UTF-8 conversion error: {0}")]
|
||||
Utf8(#[from] std::string::FromUtf8Error),
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "djls-types"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
prost = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.13"
|
|
@ -1,24 +0,0 @@
|
|||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let workspace_root = manifest_dir.parent().unwrap().parent().unwrap();
|
||||
let proto_dir = workspace_root.join("proto");
|
||||
|
||||
let protos: Vec<_> = fs::read_dir(&proto_dir)
|
||||
.unwrap()
|
||||
.filter_map(Result::ok)
|
||||
.filter(|entry| entry.path().extension().and_then(|s| s.to_str()) == Some("proto"))
|
||||
.map(|entry| entry.path())
|
||||
.collect();
|
||||
|
||||
prost_build::compile_protos(
|
||||
&protos
|
||||
.iter()
|
||||
.map(|p| p.to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
&[proto_dir],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
pub mod proto {
|
||||
include!(concat!(env!("OUT_DIR"), "/djls.rs"));
|
||||
}
|
||||
|
||||
use proto::*;
|
Loading…
Add table
Add a link
Reference in a new issue