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 if !self.options.config_file.is_empty() {
86 self.cmd.arg("-config").arg(&self.options.config_file);
87 }
88
89 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}