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

            
3
use std::convert::Infallible;
4

            
5
use bonsaidb::{
6
    cli::CommandLine,
7
    core::{
8
        actionable::async_trait,
9
        connection::{AsyncConnection, AsyncStorageConnection},
10
        schema::SerializedCollection,
11
    },
12
    local::config::Builder,
13
    server::{Backend, BackendError, CustomServer, DefaultPermissions, ServerConfiguration},
14
    AnyServerConnection,
15
};
16
use clap::Subcommand;
17

            
18
mod support;
19
use support::schema::{Shape, ShapesByNumberOfSides};
20

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

            
27
#[derive(Debug)]
28
struct CliBackend;
29

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

            
36
#[async_trait]
37
impl Backend for CliBackend {
38
    type Error = Infallible;
39
    type ClientData = ();
40

            
41
6
    async fn initialize(server: &CustomServer<Self>) -> Result<(), BackendError> {
42
12
        server.create_database::<Shape>("shapes", true).await?;
43
6
        Ok(())
44
12
    }
45
}
46

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
163
1
    Ok(())
164
1
}