Skip to main content

tsgo_client/
client.rs

1use cbor4ii::serde::DecodeError;
2
3use crate::proto::{self, Diagnostic};
4use std::convert::Infallible;
5use std::ffi::OsStr;
6use std::io::Read;
7use std::path::PathBuf;
8use std::{io, process};
9
10pub struct Client {
11    stdout: process::ChildStdout,
12    child: process::Child,
13}
14
15#[derive(Default)]
16pub struct Options {
17    pub cwd: Option<PathBuf>,
18    pub log_file: Option<String>,
19    pub config_file: String,
20}
21
22pub struct Builder {
23    cmd: process::Command,
24    options: Options,
25}
26
27pub struct UninitializedClient {
28    client: Client,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
32pub struct MessageType(u8);
33
34#[derive(thiserror::Error, Debug)]
35#[error("process error: {0}")]
36pub struct ProcessError(#[from] io::Error);
37
38#[derive(thiserror::Error, Debug)]
39#[non_exhaustive]
40pub enum ProtocolError {
41    #[error("decode error: {0}")]
42    DecodeError(DecodeError<Infallible>),
43    #[error("invalid TypeScript project with diagnostics: {0:?}")]
44    InvalidTsProject(Vec<Diagnostic>),
45    #[error("remote error message: {0}")]
46    Error(String),
47}
48
49impl Client {
50    pub fn builder(exe: &OsStr, options: Options) -> Builder {
51        let mut cmd = process::Command::new(exe);
52        cmd.arg("--api");
53        Client::with_command(cmd, options)
54    }
55
56    pub fn with_command(cmd: process::Command, options: Options) -> Builder {
57        Builder { cmd, options }
58    }
59
60    pub fn load_project<'buf>(
61        &mut self,
62        resp: &'buf mut Vec<u8>,
63    ) -> Result<proto::ProjectResponse<'buf>, ProtocolError> {
64        resp.clear();
65        self.stdout
66            .read_to_end(resp)
67            .map_err(|err| ProtocolError::Error(err.to_string()))?;
68        cbor4ii::serde::from_slice(resp).map_err(ProtocolError::DecodeError)
69    }
70}
71
72impl Builder {
73    pub fn cwd(mut self, path: PathBuf) -> Builder {
74        self.options.cwd = Some(path);
75        self
76    }
77
78    pub fn log_file(mut self, filename: String) -> Builder {
79        self.options.log_file = Some(filename);
80        self
81    }
82
83    pub fn build(mut self) -> Result<UninitializedClient, ProcessError> {
84        // Add config file argument if provided
85        if !self.options.config_file.is_empty() {
86            self.cmd.arg("-config").arg(&self.options.config_file);
87        }
88
89        // Set the working directory if provided
90        if let Some(cwd) = &self.options.cwd {
91            self.cmd.current_dir(cwd);
92        }
93
94        let mut child = self
95            .cmd
96            .stdin(process::Stdio::piped())
97            .stdout(process::Stdio::piped())
98            .stderr(process::Stdio::inherit())
99            .spawn()?;
100        let stdout = child.stdout.take().unwrap();
101
102        let client = Client { stdout, child };
103        Ok(UninitializedClient { client })
104    }
105}
106
107impl UninitializedClient {
108    pub fn init(self) -> Result<Client, ProtocolError> {
109        Ok(self.client)
110    }
111}
112
113impl Client {
114    pub fn close(mut self) -> io::Result<()> {
115        self.child.kill()?;
116        self.child.wait()?;
117        Ok(())
118    }
119}
120
121impl Drop for Client {
122    fn drop(&mut self) {
123        let _ = self.child.kill();
124    }
125}