1use std::future::Future;
4use std::pin::Pin;
5use std::time::Duration;
6
7use crate::exec::ExecError;
8
9pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Concurrency {
20 Shared,
22 Exclusive,
24}
25
26#[derive(Debug, Clone)]
32pub struct Invocation {
33 pub args: Vec<String>,
34 pub concurrency: Concurrency,
35 pub timeout: Duration,
36}
37
38impl Invocation {
39 pub fn read<I, S>(args: I) -> Self
41 where
42 I: IntoIterator<Item = S>,
43 S: Into<String>,
44 {
45 Self {
46 args: args.into_iter().map(Into::into).collect(),
47 concurrency: Concurrency::Shared,
48 timeout: crate::exec::DEFAULT_TIMEOUT,
49 }
50 }
51
52 pub fn mutate<I, S>(args: I) -> Self
54 where
55 I: IntoIterator<Item = S>,
56 S: Into<String>,
57 {
58 Self {
59 concurrency: Concurrency::Exclusive,
60 ..Self::read(args)
61 }
62 }
63
64 pub fn mutate_shared<I, S>(args: I) -> Self
73 where
74 I: IntoIterator<Item = S>,
75 S: Into<String>,
76 {
77 Self::read(args)
78 }
79
80 #[must_use]
81 pub fn with_timeout(mut self, timeout: Duration) -> Self {
82 self.timeout = timeout;
83 self
84 }
85
86 pub fn display(&self) -> String {
89 let mut out = String::from("tailscale");
90 for arg in &self.args {
91 out.push(' ');
92 out.push_str(arg);
93 }
94 out
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct Output {
101 pub exit_code: Option<i32>,
103 pub stdout: Vec<u8>,
104 pub stderr: String,
105}
106
107impl Output {
108 pub fn success(&self) -> bool {
109 self.exit_code == Some(0)
110 }
111
112 pub fn stdout_str(&self) -> std::borrow::Cow<'_, str> {
113 String::from_utf8_lossy(&self.stdout)
114 }
115}
116
117pub trait LocalBackend: Send + Sync + std::fmt::Debug {
122 fn run<'a>(&'a self, invocation: Invocation) -> BoxFuture<'a, Result<Output, ExecError>>;
123}
124
125impl<T: LocalBackend + ?Sized> LocalBackend for std::sync::Arc<T> {
126 fn run<'a>(&'a self, invocation: Invocation) -> BoxFuture<'a, Result<Output, ExecError>> {
127 (**self).run(invocation)
128 }
129}
130
131#[derive(Debug, Clone)]
138pub struct Unavailable {
139 reason: String,
140}
141
142impl Unavailable {
143 pub fn new(reason: impl Into<String>) -> Self {
144 Self {
145 reason: reason.into(),
146 }
147 }
148}
149
150impl Default for Unavailable {
151 fn default() -> Self {
152 Self::new("the local surface is not available")
153 }
154}
155
156impl LocalBackend for Unavailable {
157 fn run<'a>(&'a self, _invocation: Invocation) -> BoxFuture<'a, Result<Output, ExecError>> {
158 Box::pin(async move {
159 Err(ExecError::BinaryNotFound {
160 searched: vec![self.reason.clone()],
161 })
162 })
163 }
164}