use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
use crate::exec::ExecError;
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Concurrency {
Shared,
Exclusive,
}
#[derive(Debug, Clone)]
pub struct Invocation {
pub args: Vec<String>,
pub concurrency: Concurrency,
pub timeout: Duration,
pub stdin: Option<Vec<u8>>,
}
impl Invocation {
pub fn read<I, S>(args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
args: args.into_iter().map(Into::into).collect(),
concurrency: Concurrency::Shared,
timeout: crate::exec::DEFAULT_TIMEOUT,
stdin: None,
}
}
pub fn mutate<I, S>(args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
concurrency: Concurrency::Exclusive,
..Self::read(args)
}
}
pub fn mutate_shared<I, S>(args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self::read(args)
}
#[must_use]
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[must_use]
pub fn with_stdin(mut self, stdin: impl Into<Vec<u8>>) -> Self {
self.stdin = Some(stdin.into());
self
}
pub fn display(&self) -> String {
let mut out = String::from("tailscale");
for arg in &self.args {
out.push(' ');
out.push_str(arg);
}
out
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Output {
pub exit_code: Option<i32>,
pub stdout: Vec<u8>,
pub stderr: String,
}
impl Output {
pub fn success(&self) -> bool {
self.exit_code == Some(0)
}
pub fn stdout_str(&self) -> std::borrow::Cow<'_, str> {
String::from_utf8_lossy(&self.stdout)
}
}
pub trait LocalBackend: Send + Sync + std::fmt::Debug {
fn run<'a>(&'a self, invocation: Invocation) -> BoxFuture<'a, Result<Output, ExecError>>;
}
impl<T: LocalBackend + ?Sized> LocalBackend for std::sync::Arc<T> {
fn run<'a>(&'a self, invocation: Invocation) -> BoxFuture<'a, Result<Output, ExecError>> {
(**self).run(invocation)
}
}
#[derive(Debug, Clone)]
pub struct Unavailable {
reason: String,
}
impl Unavailable {
pub fn new(reason: impl Into<String>) -> Self {
Self {
reason: reason.into(),
}
}
}
impl Default for Unavailable {
fn default() -> Self {
Self::new("the local surface is not available")
}
}
impl LocalBackend for Unavailable {
fn run<'a>(&'a self, _invocation: Invocation) -> BoxFuture<'a, Result<Output, ExecError>> {
Box::pin(async move {
Err(ExecError::BinaryNotFound {
searched: vec![self.reason.clone()],
})
})
}
}