1
1
//! Shows how to utilize the built-in CLI helpers.
2

            
3
use bonsaidb::{
4
    cli::CommandLine,
5
    core::{
6
        actionable::async_trait,
7
        connection::{Connection, StorageConnection},
8
        schema::SerializedCollection,
9
    },
10
    local::config::Builder,
11
    server::{Backend, CustomServer, DefaultPermissions, NoDispatcher, ServerConfiguration},
12
    AnyServerConnection,
13
};
14
use clap::Subcommand;
15

            
16
mod support;
17
use support::schema::{Shape, ShapesByNumberOfSides};
18

            
19
#[tokio::main]
20
async fn main() -> anyhow::Result<()> {
21
    env_logger::init();
22
    CliBackend.run().await
23
}
24

            
25
#[derive(Debug)]
26
struct CliBackend;
27

            
28
12
#[derive(Debug, Subcommand)]
29
enum Cli {
30
4
    Add { sides: u32 },
31
    Count { sides: Option<u32> },
32
}
33

            
34
#[async_trait]
35
impl Backend for CliBackend {
36
    type CustomApi = ();
37
    type ClientData = ();
38
    type CustomApiDispatcher = NoDispatcher<Self>;
39

            
40
6
    async fn initialize(server: &CustomServer<Self>) {
41
6
        server
42
6
            .create_database::<Shape>("shapes", true)
43
4
            .await
44
6
            .unwrap();
45
6
    }
46
}
47

            
48
#[async_trait]
49
impl CommandLine for CliBackend {
50
    type Backend = Self;
51
    type Subcommand = Cli;
52

            
53
6
    async fn configuration(&mut self) -> anyhow::Result<ServerConfiguration> {
54
6
        Ok(ServerConfiguration::new("cli.bonsaidb")
55
6
            .default_permissions(DefaultPermissions::AllowAll)
56
6
            .with_schema::<Shape>()?)
57
12
    }
58

            
59
3
    async fn execute(
60
3
        &mut self,
61
3
        command: Self::Subcommand,
62
3
        connection: AnyServerConnection<Self>,
63
3
    ) -> anyhow::Result<()> {
64
3
        let database = connection.database::<Shape>("shapes").await?;
65
3
        match command {
66
2
            Cli::Add { sides } => {
67
2
                let new_shape = Shape::new(sides).push_into(&database).await?;
68
2
                println!(
69
2
                    "Shape #{} inserted with {} sides",
70
2
                    new_shape.header.id, sides
71
2
                );
72
            }
73
1
            Cli::Count { sides } => {
74
1
                if let Some(sides) = sides {
75
                    let count = database
76
                        .view::<ShapesByNumberOfSides>()
77
                        .with_key(sides)
78
                        .reduce()
79
                        .await?;
80
                    println!("Found {} shapes with {} sides", count, sides);
81
1
                } else {
82
1
                    let count = database.view::<ShapesByNumberOfSides>().reduce().await?;
83
1
                    println!("Found {} shapes with any number of sides", count);
84
                }
85
            }
86
        }
87
3
        Ok(())
88
6
    }
89
}
90

            
91
1
#[tokio::test]
92
1
async fn test() -> anyhow::Result<()> {
93
1
    if std::path::Path::new("cli.bonsaidb").exists() {
94
        tokio::fs::remove_dir_all("cli.bonsaidb").await?;
95
1
    }
96
1
    if std::path::Path::new("cli-backup").exists() {
97
        tokio::fs::remove_dir_all("cli-backup").await?;
98
1
    }
99

            
100
1
    CliBackend
101
31
        .run_from(["executable", "server", "certificate", "install-self-signed"])
102
31
        .await?;
103

            
104
    // Execute a command locally (no server running).
105
16
    CliBackend.run_from(["executable", "add", "3"]).await?;
106

            
107
    // Spawn the server so that we can communicate with it over the network
108
1
    let server_task = tokio::task::spawn(async {
109
1
        CliBackend
110
20
            .run_from(["executable", "server", "serve", "--listen-on", "6004"])
111
20
            .await
112
            .unwrap();
113
1
    });
114
1

            
115
1
    // Execute the same command as before, but this time use the network.
116
1
    CliBackend
117
1
        .run_from([
118
1
            "executable",
119
1
            "--url",
120
1
            "bonsaidb://localhost:6004",
121
1
            "--pinned-certificate",
122
1
            "cli.bonsaidb/pinned-certificate.der",
123
1
            "add",
124
1
            "3",
125
2
        ])
126
2
        .await?;
127

            
128
1
    CliBackend
129
1
        .run_from([
130
1
            "executable",
131
1
            "--url",
132
1
            "bonsaidb://localhost:6004",
133
1
            "--pinned-certificate",
134
1
            "cli.bonsaidb/pinned-certificate.der",
135
1
            "count",
136
2
        ])
137
2
        .await?;
138

            
139
    // Close the server
140
1
    server_task.abort();
141
1
    drop(server_task.await);
142

            
143
    // Back up the database
144
1
    CliBackend
145
42
        .run_from(["executable", "server", "backup", "path", "cli-backup"])
146
42
        .await?;
147

            
148
    // Remove the database and restore from backup.
149
1
    tokio::fs::remove_dir_all("cli.bonsaidb").await?;
150

            
151
    // Restore the database
152
1
    CliBackend
153
71
        .run_from(["executable", "server", "restore", "path", "cli-backup"])
154
71
        .await?;
155

            
156
    // Re-open the database and verify the operations were made.
157
    {
158
14
        let server = CliBackend.open_server().await?;
159
1
        let database = server.database::<Shape>("shapes").await?;
160
2
        assert_eq!(database.view::<ShapesByNumberOfSides>().reduce().await?, 2);
161
    }
162
1
    tokio::fs::remove_dir_all("cli-backup").await?;
163

            
164
1
    Ok(())
165
1
}