holochain_cli_sandbox/
lib.rs

1#![warn(missing_docs)]
2
3//! # holochain_cli_sandbox
4//!
5//! A library and CLI to help create, run, and interact with sandboxed Holochain conductor environments,
6//! for testing and development purposes.
7//! **Warning: this is still WIP and subject to change**
8//! There's probably a few bugs. If you find one please open an [issue](https://github.com/holochain/holochain/issues)
9//! or make a PR.
10//!
11//! While this crate can be compiled into an executable, it can also be used as a library so you can create more
12//! complex sandboxes / admin calls.
13//! See the docs:
14//!
15//! ```shell
16//! cargo doc --open
17//! ```
18//!
19//! and the examples.
20
21use std::path::Path;
22
23use holochain_conductor_api::conductor::paths::ConfigRootPath;
24use holochain_conductor_api::{AdminRequest, AdminResponse};
25use holochain_websocket::{WebsocketResult, WebsocketSender};
26use ports::get_admin_api;
27
28pub use ports::force_admin_port;
29
30/// Print a message with `hc-sandbox: ` prepended
31/// and ANSI colors.
32macro_rules! msg {
33    ($($arg:tt)*) => ({
34        use ansi_term::Color::*;
35        print!("{} ", Blue.bold().paint("hc-sandbox:"));
36        println!($($arg)*);
37    })
38}
39
40pub mod bundles;
41pub mod calls;
42pub mod cli;
43#[doc(hidden)]
44pub mod cmds;
45pub mod run;
46pub mod sandbox;
47pub mod save;
48pub use cli::HcSandbox;
49use holochain_trace::Output;
50
51mod ports;
52mod zome_call;
53
54/// An active connection to a running conductor.
55pub struct CmdRunner {
56    client: WebsocketSender,
57    task: tokio::task::JoinHandle<()>,
58}
59
60impl Drop for CmdRunner {
61    fn drop(&mut self) {
62        self.task.abort();
63    }
64}
65
66impl CmdRunner {
67    const HOLOCHAIN_PATH: &'static str = "holochain";
68    /// Create a new connection for calling admin interface commands.
69    /// Panics if admin port fails to connect.
70    pub async fn new(port: u16) -> Self {
71        Self::try_new(port)
72            .await
73            .expect("Failed to create CmdRunner because admin port failed to connect")
74    }
75
76    /// Create a new connection for calling admin interface commands.
77    pub async fn try_new(port: u16) -> WebsocketResult<Self> {
78        let (client, task) = get_admin_api(port).await?;
79        Ok(Self { client, task })
80    }
81
82    /// Create a command runner from a sandbox path.
83    /// This expects holochain to be on the path.
84    pub async fn from_sandbox(
85        sandbox_path: ConfigRootPath,
86    ) -> anyhow::Result<(Self, tokio::process::Child)> {
87        Self::from_sandbox_with_bin_path(Path::new(Self::HOLOCHAIN_PATH), sandbox_path).await
88    }
89
90    /// Create a command runner from a sandbox path and
91    /// set the path to the holochain binary.
92    pub async fn from_sandbox_with_bin_path(
93        holochain_bin_path: &Path,
94        sandbox_path: ConfigRootPath,
95    ) -> anyhow::Result<(Self, tokio::process::Child)> {
96        let conductor = run::run_async(holochain_bin_path, sandbox_path, None, Output::Log).await?;
97        let cmd = CmdRunner::try_new(conductor.0).await?;
98        Ok((cmd, conductor.1))
99    }
100
101    /// Make an Admin request to this conductor.
102    pub async fn command(&mut self, cmd: AdminRequest) -> anyhow::Result<AdminResponse> {
103        let response: Result<AdminResponse, _> = self.client.request(cmd).await;
104        Ok(response?)
105    }
106}
107
108#[macro_export]
109/// Expect that an enum matches a variant and panic if it doesn't.
110macro_rules! expect_variant {
111    ($var:expr => $variant:path, $error_msg:expr) => {
112        match $var {
113            $variant(v) => v,
114            _ => panic!(format!("{}: Expected {} but got {:?}", $error_msg, stringify!($variant), $var)),
115        }
116    };
117    ($var:expr => $variant:path) => {
118        expect_variant!($var => $variant, "")
119    };
120}
121
122#[macro_export]
123/// Expect that an enum matches a variant and return an error if it doesn't.
124macro_rules! expect_match {
125    ($var:expr => $variant:path, $error_msg:expr) => {
126        match $var {
127            $variant(v) => v,
128            _ => anyhow::bail!("{}: Expected {} but got {:?}", $error_msg, stringify!($variant), $var),
129        }
130    };
131    ($var:expr => $variant:path) => {
132        expect_variant!($var => $variant, "")
133    };
134}