# CLI

Awesomesauce. We're almost there to a functioning... thing? App? Yes. A functioning app.

The server is all deployed, let's build the cli. Start by making a new `src/cli.rs` file with the following contents:

```rust
#[tokio::main]
async fn main() {
    println!("Hello, world!")
}
```

Now we need to tell `Cargo.toml` about the new binary target, so add the following to `Cargo.toml`.&#x20;

```toml
[[bin]]
name = "cli"
path = "src/cli.rs"
```

Come to think of it, we'll want some command line argument parsing too, so let's throw that dependency in there now:

```bash
cargo add clap --features derive
```

While we're at it, add `reqwest` too. `reqwest` is a http request library based on `hyper`.

```bash
cargo add reqwest --features json
```

Great! So we'll want to basically have two commands:

1. Store a secret
2. Fetch a secret

Let's start by adding some `clap` library magic. In the `cli.rs` file, add this bit at the top:

```rust
use std::process::exit;

use clap::Parser;
use uuid::Uuid;

#[derive(Parser, Debug)]
struct Args {
    #[arg(short, long)]
    store: Option<String>,
    #[arg(short, long)]
    fetch: Option<Uuid>,
    #[arg(short, long)]
    code: Option<String>,
}
```

This parses command line arguments and basically accepts the flags "--store", "--fetch", and "--code".

Now, the rest of this code isn't very interesting, so I'll drop it below. Basically it checks whether you have added the store or fetch arguments, and makes the request to our server accordingly. When you write this code, remember to update the `SERVER_URI` variable!

```rust
static SERVER_URI: &'static str = "https://carrier-pigeon.shuttleapp.rs";

#[tokio::main]
async fn main() {
    let Args { store, fetch, code } = Args::parse();

    if store.is_none() && (fetch.is_none() || code.is_none()) {
        println!("You must specify either --store or --fetch and --code.");
        exit(1);
    }

    let client = Client::new();
    if store.is_some() {
        let response = client
            .post(format!("{}/secrets", SERVER_URI))
            .json(&CreateSecretReq {
                secret: store.unwrap(),
                expires_at: Utc::now() + Duration::days(1),
            })
            .send()
            .await
            .expect("Received a bad response while storing secret")
            .json::<CreateSecretRes>()
            .await
            .expect("Could not parse result from storing secret");

        println!(
            "Success! Share this id and code with the counterparty:
id: {id}
code: {code}

or, share this URL with the counterparty:

https://carrier-pigeon.shuttle.rs/{id}?code={code}",
            id = response.id,
            code = response.code
        );
    } else {
        let response = client
            .get(format!(
                "{}/secrets/{}?code={}",
                SERVER_URI,
                fetch.unwrap(),
                code.unwrap()
            ))
            .send()
            .await
            .expect("Received a bad response while fetching secret")
            .json::<GetSecretRes>()
            .await
            .expect("Could not parse result from fetching secret");

        println!("{}", response.secret);
    }
}

```

Now simply run the command to store,

```
cargo run --bin=cli -- --store='super secret test message'
```

and use the resulting id and code to fetch it

```
cargo run --bin=cli -- --fetch=some-long-id --code=some-medium-string
```

Your results should be "super secret test message". You did it!! Congratulations!!

{% hint style="info" %}
This is the end of the tutorial, but there is extra credit for you overachievers! You might have noticed we added an `expired_at` to the secrets that we store in the database. Well, there's a reason -- to make this a bit more usable and secure, secrets should have expirations associated with them. If you feel up to it, see if you can add a bit to the query that actually checks the expiration time, and then a bit to the CLI that accepts an expiration time. If you're feeling fancy, the expiration could even parse human times (like 5 minutes, 2 days, or tomorrow!)

There are lots of other things we could do with this basic setup too -- for example, we could check the `Accept` header and return a fully rendered HTML page if it's `text/html` or a JSON blob if it's `application/json`. Have fun!
{% endhint %}
