A Rust CLI for decoding certs
Tags: rust, certificate, tdd
Published: 2020-06-02 17:40:03

---
title: A Rust CLI for decoding certs
series: "TDD and Rust: A CLI for decoding certs"
published: true
description: A Rust CLI for decoding certs developed with a Test Driven Development workflow.
tags: rust,certificate,tdd
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/6eobh8i4c0uvh5d39t8m.jpeg
---

# Introduction
In this series of posts, we'll start to build a simple cli in [Rust](https://www.rust-lang.org/) for decoding X.509 certificates. I'll try to keep it as beginner-friendly as possible, by explaining things as best I can when they may be unclear. Some basic Rust knowledge should hopefully be all you need. Feel free to ask any questions in the comments below!  

> **:memo: Note**
>
> If you are completely new to Rust and don't even have it installed, you can install it using [rustup](https://rustup.rs/). You should hopefully still be able to follow along. 

This first post will mainly focus on a Test Driven Development workflow. I wrote this as I was developing so mistakes or things which I overlooked and ended up factoring out afterward are all kept in.

Table of Contents
=================

   * [What are we building?](#what-are-we-building)
   * [Some constraints to simplify the cli](#some-constraints-to-simplify-the-cli)
   * [Test list](#test-list)
   * [Validate args](#validate-args)
      * [A note on this workflow](#a-note-on-this-workflow)
      * [Add an error message](#add-an-error-message)
      * [Check argument is a path that exists](#check-argument-is-a-path-that-exists)
      * [Factor out IO](#factor-out-io)
      * [Check argument is a file](#check-argument-is-a-file)
      * [Another small refactor](#another-small-refactor)
   * [In the next post](#in-the-next-post)


# What are we building?
I generally use the `openssl` cli for pulling information out of certificates. Specifically the `x509` command, for example:

```bash
# get certificate
$ openssl s_client -connect google.com:443 2>/dev/null < /dev/null \
    | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > google.com.crt 

# extract info
$ openssl x509 -text -noout -in google.com.crt | head -n 5
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            1a:86:8b:0d:af:9b:c7:34:08:00:00:00:00:3e:bd:97
```

All this does is print out a certificate as a human-readable string. I've written about what information certificates contain [here](https://dev.to/wayofthepie/structure-of-an-ssl-x-509-certificate-16b) if interested. What we want to do in the next few posts is to see if we can build something similar to the `openssl x509 -text` output from above in a Rust cli. 

# Some constraints to simplify the cli
Let's think about the API for this cli, how a user will call it. To keep things simple, it should just take a file path to a single certificate. So we should end up with a call like so:

```bash
$ cert-decoder /path/to/some/cert.pem
```
Another constraint to keep this simple at the beginning is the encoding of the certificate. Let's say the certificate must be PEM [^pem] (Privacy Enhanced Mail) encoded. This is the encoding you generally see certificates with. It's a string which begins with a line comprised of `-----BEGIN CERTIFICATE-----`, then some base64 encoded DER (Distinguished Encoding Rules) and ends with a line comprised of `-----END CERTIFICATE-----`. It looks as follows, with some of the base64 string stripped out and replaced with `...`:

```plaintext
-----BEGIN CERTIFICATE-----
MIIJTzCCCDegAwIBAgIQGoaLDa+bxzQIAAAAAD69lzANBgkqhkiG9w0BAQsFADBC
...
DMCTA95gzVKezFCaUidRU9UyHOFzltfYDt7HRlp7MwWoPLM=
-----END CERTIFICATE-----
```

# Test list
We have a basic API and some constraints now. Let's make a test list before we do anything else. This is simply a list of tests we should write to cover some piece of functionality. Let's just cover the most simple thing initially, validating the arguments. 

---
**Validate args**  
- ☐ Only allow a single argument  
- ☐ Argument is a path that exists  
- ☐ Argument should be a single file 

---

Implementing this we can start to use it and see where we should go next.

# Validate args
First, create a new project with `cargo new cert-decoder`. `main.rs`  should look as follows initially.

 ```rust
fn main() {
    println!("Hello, world!")
}
```

Let's create a test for the first item on our test list.

```rust
fn main() {
    println!("Hello, world!")
}

#[cfg(test)]
mod test {

    #[test]
    fn should_error_if_not_given_a_single_argument() {
        // create fake args list, with no args
        // run function with args
        // check that it returns an error
    }
}
```
Above I've outlined what we need to do in this test in comments. It's not easy to test this through the `main` function as `main` does not take any arguments in its signature. In Rust to read program arguments you use the function [std::env::args](https://doc.rust-lang.org/stable/std/env/fn.args.html), more on that later.

Let's create a new function called `execute`. We know we want this function to return an error so we will make it return a [Result](https://doc.rust-lang.org/std/result/enum.Result.html).

```rust
fn execute(args: Vec<String>) -> Result<(), ()> {
    Ok(())
}

fn main() {
    println!("Hello, world!")
}

#[cfg(test)]
mod test {

    #[test]
    fn should_error_if_not_given_a_single_argument() {
        // create fake args list, with no args
        // run function with args
        // check that it returns an error
    }
}
```
For now execute just returns an `Ok` result with a value of **[()](https://doc.rust-lang.org/std/primitive.unit.html)**. This is called the _unit_ type. Now let's write our test.

```rust 
fn execute(args: Vec<String>) -> Result<(), ()> {
    Ok(())
}

fn main() {
    println!("Hello, world!")
}

#[cfg(test)]
mod test {

    use crate::execute;

    #[test]
    fn should_error_if_not_given_a_single_argument() {
        let args = Vec::new();
        let result = execute(args);
        assert!(result.is_err());
    }
}
```

If we run this with `cargo -q test`[^cargoq] it will fail. There will also be a warning about the `args` parameter in `execute` not being used, but we can ignore that for now.

```bash
➜ cargo -q test
...
running 1 test
F
failures:

---- test::should_error_if_not_given_a_single_argument stdout ----
thread 'test::should_error_if_not_given_a_single_argument' panicked at 'assertion failed: result.is_err()', src/ma
in.rs:18:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    test::should_error_if_not_given_a_single_argument

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--bin cert-decode'
```

Let's make it pass by making a tiny change.

```rust 
fn execute(args: Vec<String>) -> Result<(), ()> {
    Err(())
}

fn main() {
    println!("Hello, world!")
}

#[cfg(test)]
mod test {

    use crate::execute;

    #[test]
    fn should_error_if_not_given_a_single_argument() {
        let args = Vec::new();
        let result = execute(args);
        assert!(result.is_err());
    }
}
```

Let's re-run and it should be green.

```bash
➜ cargo -q test
...
running 1 test
.
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

Great! Now we just need to make it return an error only in the case we mention in our first test list item. When we do not receive just a single argument.

```rust 
fn execute(args: Vec<String>) -> Result<(), ()> {
    if args.len() != 1 {
        return Err(());
    }
    Ok(())
}
```

Re-run and it should still be green.

```bash
➜ cargo -q test

running 1 test
.
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
Awesome! We should change this to return an error message. But first a quick note about this workflow.

## A note on this workflow
This is how I generally write code. What's the smallest useful change I can make to add functionality? In this case, to get started it was argument validation. With that in mind, I decomposed this further into items that look like individually testable parts of the behavior we want in argument validation. I made a list of them so I can tick them off as I go. This helps to keep the focus on a single small piece of functionality. It's especially useful when decomposing complex changes.

To implement, write a test that specifies the behavior of one of the items. Then, make it fail, make it pass with _any_ code, change to implement the real behavior, and finally refactor where appropriate.

## Add an error message
Let's improve the argument length check by returning a useful error message. One thing I always try to keep in mind is to make the error message actionable. For example, here we could just say "Error: did not receive a single argument.". However, it would be better to also add an action - "Error: did not receive a single argument. Please invoke cert-decoder as follows: './cert-decoder /path/to/cert'.". Not only are we telling a user what went wrong, but we are also telling them how to fix it.

Let's update our test.

```rust
#[test]
fn should_error_if_not_given_a_single_argument() {
    // arrange
    let args = Vec::new();

    // act
    let result = execute(args);

    // assert
    assert!(result.is_err());
    assert_eq!(
        result.err().unwrap(),
        format!(
            "{}{}",
            "Error: did not receive a single argument, ",
            "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
        )
    );
}
```

> **:memo: Note**
> 
> [result.err()](https://doc.rust-lang.org/std/result/enum.Result.html#method.is_err) will return an [Option](https://doc.rust-lang.org/std/option/enum.Option.html). The `Option` will be `Some` if the value of the `Result` is `Err`. This `Some` will contain the same value `Err` contained. The `Option` will be `None` if the `Result` is `Ok`. 
> 
> In this case we know for certain the `Result`'s value is `Err` as we asserted it before with [is_err](https://doc.rust-lang.org/std/result/enum.Result.html#method.is_err). So the `Option` returned from `result.err()` will be `Some` and we can safely call [unwrap](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap) on that `Some` to get the value it contains. 

This won't compile because we are trying to compare two different types now. `result.err().unwrap()` will return `()` and we are trying to compare this to a string. So we need to update the return type of `execute`. We could also add the correct return value, the string we are asserting `execute` returns. But I like to do things in tiny changes so let's just update the type first and return an empty string. 

```rust
fn execute(args: Vec<String>) -> Result<(), String> {
    if args.len() != 1 {
        return Err("".to_owned());
    }
    Ok(())
}
```
Now if we run `cargo -q test` it will compile but the test will fail.

```plaintext
➜ cargo -q test

running 1 test
F
failures:

---- test::should_error_if_not_given_a_single_argument stdout ----
thread 'test::should_error_if_not_given_a_single_argument' panicked at 'assertion failed: `(left == right)`
  left: `""`,
 right: `"Error: did not receive a single argument, please invoke cert-decoder as follows: ./cert-decoder /path/to/cert"`', src/main.rs:27:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
...
```
So let's make some changes.

```rust
fn execute(args: Vec<String>) -> Result<(), String> {
    if args.len() != 1 {
        let error = format!(
            "{}{}",
            "Error: did not receive a single argument, ",
            "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
        );
        return Err(error);
    }
    Ok(())
}
```
And re-run.

```bash
➜ cargo -q test

running 1 test
.
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
Great! Our test is green and we have an argument length check with a nice error message. It won't do anything if we try to run it though. To fix that, let's update `main`.

```rust
fn main() -> Result<(), String> {
    let args = std::env::args().skip(1).collect();
    execute(args)
}
```
Here we add a real call to get the args in `main`, `std::env::args().skip(1).collect()`. The name of the binary will be part of the args, that is why we skip one argument.

> **:memo: Note** 
>
> Let's breakdown the `std::env::args().skip(1).collect()`. If you understood it you can skip this note. 
>
> `std::env::args()` will return a value of type [Args](https://doc.rust-lang.org/stable/std/env/struct.Args.html). `Args` implements the [Iterator](https://doc.rust-lang.org/std/iter/trait.Iterator.html) trait, which means it has [skip](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.skip) and [collect](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect) methods. We skip one item, which is the binary name and collect the rest. Because we are using this as an argument to `execute`, `Rust` can infer its type which is `Vec<String>`.

Now we can run with no argument using `cargo run` and it will give an error.

```bash
➜ cargo -q run
Error: "Error: did not receive a single argument, please invoke cert-decoder as follows: ./cert-decoder /path/to/c
ert."
```
And if we pass an argument it will print nothing.
```bash
➜ cargo -q run -- something

```
Anything after the `--` will be passed to our program as arguments. We can check off the first item on our test list now.

---
**Validate args**  
- :heavy_check_mark: Only allow a single argument  
- ☐ Argument is a path that exists  
- ☐ Argument should be a single file 

---

## Check argument is a path that exists
First, a test.

```rust
#[test]
fn should_error_if_argument_is_not_a_path_which_exists() {
    // arrange
    let args = vec!["does-not-exist".to_owned()];

    // act
    let result = execute(args);

    // assert
    assert!(result.is_err());
    assert_eq!(
        result.err().unwrap(),
        "Error: path given as argument does not exist, it must be a path to a certificate!"
    );
}
```

As always, run the test to see it fail first. It will fail because the result will not be an error. Let's make it return an error. 

```rust
use std::path::Path;

fn execute(args: Vec<String>) -> Result<(), String> {
    if args.len() != 1 {
        let error = format!(
            "{}{}",
            "Error: did not receive a single argument, ",
            "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
        );
        return Err(error);
    }
    let path = Path::new(&args[0]);
    if !path.exists() {
        return Err(
            "Error: path given as argument does not exist, it must be a path to a certificate!"
                .to_owned(),
        );
    }
    Ok(())
}
```
Now the test will pass. [Path](https://doc.rust-lang.org/std/path/struct.Path.html) contains many operations for interacting with filesystems. The call to [exists](https://doc.rust-lang.org/std/path/struct.Path.html#method.exists) above will check the real filesystem to see if the path we give as an argument exists. This is generally not something you want to do in your tests. 

Before we factor out this IO, let's tick this item off on our test list as it is working.

---
**Validate args**  
- :heavy_check_mark: Only allow a single argument  
- :heavy_check_mark: Argument is a path that exists  
- ☐ Argument should be a single file 

---

The code up to this point can be seen [here](https://github.com/wayofthepie/cert-decoder/tree/b5c242bb92eb125117554d2692f5bc8c93082ca3).


## Factor out IO
Almost any code you write is going to do some kind of IO, e.g. network calls, file reads. Normally we don't want this to happen in a test. Beyond just keeping IO out of tests, making it clearer where IO does happen is very useful for readability and refactoring. Especially as a system grows.

Right now what we want to do is make our code testable under different scenarios without touching the real filesystem. There are a few ways to do this, but I generally use traits and have a real and fake implementation of the trait. Let's define a trait that has an `exists` method, just like `Path`.


```rust
trait PathValidator {
    fn exists(&self, path: &str) -> bool;
}
```
It took me a while to come up with a name I was somewhat happy with for this trait. Naming is hard. Now that we have a trait, let's implement it for the real case.

```rust
struct CertValidator;

impl PathValidator for CertValidator {
    fn exists(&self, path: &str) -> bool {
        Path::new(path).exists()
    }
}
```

Now our tests need a version of this also.

```rust
#[cfg(test)]
mod test {

    use crate::{execute, PathValidator};

    struct FakeValidator {
        is_path: bool,
    }

    impl PathValidator for FakeValidator {
        fn exists(&self, _: &str) -> bool {
            self.is_path
        }
    }   
    ...
}
```

Finally, we need to refactor the `execute` function to take something that implements `PathValidator` as a parameter and update our tests to reflect this. The change as a whole is as follows, with some notes in comments.

```rust
use std::path::Path;

trait PathValidator {
    fn exists(&self, path: &str) -> bool;
}

struct CertValidator;

impl PathValidator for CertValidator {
    fn exists(&self, path: &str) -> bool {
        Path::new(path).exists()
    }
}

// Here we change the signature of execute to also take a value of type `impl PathValidator`, 
// see the note after this code block for more information. 
fn execute(validator: impl PathValidator, args: Vec<String>) -> Result<(), String> {
    if args.len() != 1 {
        let error = format!(
            "{}{}",
            "Error: did not receive a single argument, ",
            "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
        );
        return Err(error);
    }
    let path = &args[0];

    // Instead of calling Path's exists method, we call exists on our 
    // PathValidator implementation.
    if !validator.exists(path) {
        return Err(
            "Error: path given as argument does not exist, it must be a path to a certificate!"
                .to_owned(),
        );
    }
    Ok(())
}

fn main() -> Result<(), String> {
    let args = std::env::args().skip(1).collect();

    // Here we create our real PathValidator implementation,
    // CertValidator, which touches the filesystem.
    let validator = CertValidator;
    execute(validator, args)
}

#[cfg(test)]
mod test {

    use crate::{execute, PathValidator};

    struct FakeValidator {
        is_path: bool,
    }

    impl PathValidator for FakeValidator {
        fn exists(&self, _: &str) -> bool {
            self.is_path
        }
    }

    #[test]
    fn should_error_if_not_given_a_single_argument() {
        // arrange
        let args = Vec::new();

        // We construct a FakeValidator that says all paths exist.
        // It will never be called in this test, however.
        let validator = FakeValidator { is_path: true };

        // act
        let result = execute(validator, args);

        // assert
        assert!(result.is_err());
        assert_eq!(
            result.err().unwrap(),
            format!(
                "{}{}",
                "Error: did not receive a single argument, ",
                "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
            )
        );
    }

    #[test]
    fn should_error_if_argument_is_not_a_path_which_exists() {
        // arrange
        let args = vec!["does-not-exist".to_owned()];

        // We construct a validator that says no path exists.
        // This will cause our test to fail and return the error we want,
        // mimicing a path which does not exist without touching the real
        // filesystem.
        let validator = FakeValidator { is_path: false };

        // act
        let result = execute(validator, args);

        // assert
        assert!(result.is_err());
        assert_eq!(
            result.err().unwrap(),
            "Error: path given as argument does not exist, it must be a path to a certificate!"
        );
    }
}
```

> :memo: Note
>
> The type of `validator` in the `execute` function is `impl PathValidator`. This simply says `validator` can be a value of any type which implements `PathValidator`. You can read more about this in the [Rust book](https://doc.rust-lang.org/book/title-page.html) in the section on [Traits as Parameters](https://doc.rust-lang.org/book/ch10-02-traits.html?highlight=impl,trait#traits-as-parameters).

Run the tests again and they should still be green! If you run with `cargo run` as we did earlier, it should still work as expected. The code up to this point can be seen [here](https://github.com/wayofthepie/cert-decoder/tree/19fee59da8d1e4625e2693c1e388be1912e2d9ee).

## Check argument is a file
What's left on our test list?

---
**Validate args**  
- :heavy_check_mark: Only allow a single argument  
- :heavy_check_mark: Argument is a path that exists  
- ☐ Argument should be a single file 

---

Right now the argument we pass can be several file types other than a regular file, for example, a directory. As we only validate if the given path exists, not what that path points to. So let's validate it is also a file. First, a test.

```rust
#[cfg(test)]
mod test {

    use crate::{execute, PathValidator};

    struct FakeValidator {
        is_path: bool,
        is_file: bool,
    }

    impl PathValidator for FakeValidator {
        fn exists(&self, _: &str) -> bool {
            self.is_path
        }

        fn is_file(&self, _: &str) -> bool {
            self.is_file
        }
    }

    ... 

    #[test]
    fn should_error_if_argument_is_not_a_regular_file() {
        // arrange
        let args = vec!["not-a-regular-file".to_owned()];
        let validator = FakeValidator {
            is_path: true,
            is_file: false,
        };

        // act
        let result = execute(validator, args);

        // assert
        assert!(result.is_err());
        assert_eq!(
            result.err().unwrap(),
            "Error: path given is not a regular file, please update to point to a certificate."
        );
    }
}
```

This won't compile as there is no `is_file` method on our `PathValidator` trait. Here I've also updated the `FakeValidator` in the other test, setting `is_file` to `false`. Let's update that and implement the real version next.

```rust
trait PathValidator {
    fn exists(&self, path: &str) -> bool;
    fn is_file(&self, path: &str) -> bool;
}

struct CertValidator;

impl PathValidator for CertValidator {
    fn exists(&self, path: &str) -> bool {
        Path::new(path).exists()
    }

    fn is_file(&self, path: &str) -> bool {
        Path::new(path).is_file()
    }
}
```
Now we have a new `is_file` method. If we re-run the test it should fail as we are not returning an error. You can run a single test with `cargo test` by passing its name, or a string that is contained in its name. 

```bash
➜ cargo -q test should_error_if_argument_is_not_a_regular_file

running 1 test
F
failures:

---- test::should_error_if_argument_is_not_a_regular_file stdout ----
thread 'test::should_error_if_argument_is_not_a_regular_file' panicked at 'assertion failed: result.is_err()', src
/main.rs:122:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    test::should_error_if_argument_is_not_a_regular_file

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 2 filtered out

error: test failed, to rerun pass '--bin cert-decode'
```

Let's make it return an error if the path given is not a regular file.

```rust
fn execute(validator: impl PathValidator, args: Vec<String>) -> Result<(), String> {
    if args.len() != 1 {
        let error = format!(
            "{}{}",
            "Error: did not receive a single argument, ",
            "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
        );
        return Err(error);
    }
    let path = &args[0];
    if !validator.exists(path) {
        return Err(
            "Error: path given as argument does not exist, it must be a path to a certificate!"
                .to_owned(),
        );
    }
    if !validator.is_file(path) {
        return Err(
            "Error: path given is not a regular file, please update to point to a certificate."
                .to_owned(),
        );
    }
    Ok(())
}
```
Re-run and all tests should be green.
```bash
➜ cargo -q test

running 3 tests
...
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

```

Great! The code up to this point can be seen [here](https://github.com/wayofthepie/cert-decoder/tree/a992dd190de61a922cec95233e0fdbd43c500a8f). We can now tick this off on the test list.

---
**Validate args**  
- :heavy_check_mark: Only allow a single argument  
- :heavy_check_mark: Argument is a path that exists  
- :heavy_check_mark: Argument should be a single file 

---


## Another small refactor
We can refactor this a bit. If we have a regular file then it must be a path that exists. This means we can get rid of the exists check and its test entirely. Here is a diff of this change.

```diff
diff --git a/src/main.rs b/src/main.rs
index 171545b..2ec2d92 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,17 +1,12 @@
 use std::path::Path;
 
 trait PathValidator {
-    fn exists(&self, path: &str) -> bool;
     fn is_file(&self, path: &str) -> bool;
 }
 
 struct CertValidator;
 
 impl PathValidator for CertValidator {
-    fn exists(&self, path: &str) -> bool {
-        Path::new(path).exists()
-    }
-
     fn is_file(&self, path: &str) -> bool {
         Path::new(path).is_file()
     }
@@ -27,12 +22,6 @@ fn execute(validator: impl PathValidator, args: Vec<String>) -> Result<(), Strin
         return Err(error);
     }
     let path = &args[0];
-    if !validator.exists(path) {
-        return Err(
-            "Error: path given as argument does not exist, it must be a path to a certificate!"
-                .to_owned(),
-        );
-    }
     if !validator.is_file(path) {
         return Err(
             "Error: path given is not a regular file, please update to point to a certificate."
@@ -54,15 +43,10 @@ mod test {
     use crate::{execute, PathValidator};
 
     struct FakeValidator {
-        is_path: bool,
         is_file: bool,
     }
 
     impl PathValidator for FakeValidator {
-        fn exists(&self, _: &str) -> bool {
-            self.is_path
-        }
-
         fn is_file(&self, _: &str) -> bool {
             self.is_file
         }
@@ -72,10 +56,7 @@ mod test {
     fn should_error_if_not_given_a_single_argument() {
         // arrange
         let args = Vec::new();
-        let validator = FakeValidator {
-            is_path: true,
-            is_file: false,
-        };
+        let validator = FakeValidator { is_file: false };
 
         // act
         let result = execute(validator, args);
@@ -92,34 +73,11 @@ mod test {
         );
     }
 
-    #[test]
-    fn should_error_if_argument_is_not_a_path_which_exists() {
-        // arrange
-        let args = vec!["does-not-exist".to_owned()];
-        let validator = FakeValidator {
-            is_path: false,
-            is_file: false,
-        };
-
-        // act
-        let result = execute(validator, args);
-
-        // assert
-        assert!(result.is_err());
-        assert_eq!(
-            result.err().unwrap(),
-            "Error: path given as argument does not exist, it must be a path to a certificate!"
-        );
-    }
-
     #[test]
     fn should_error_if_argument_is_not_a_regular_file() {
         // arrange
         let args = vec!["not-a-regular-file".to_owned()];
-        let validator = FakeValidator {
-            is_path: true,
-            is_file: false,
-        };
+        let validator = FakeValidator { is_file: false };
 
         // act
         let result = execute(validator, args);
```
The code up to this point can be seen [here](https://github.com/wayofthepie/cert-decoder/tree/5c79d66dc65f42ad3750d776f1fafa8994355fb8).

# In the next post
In the next few posts, we'll look at reading information out of the certificate using the [x509_parser](https://docs.rs/x509-parser/0.7.0/x509_parser/) crate. We'll also switch to using [structopt](https://docs.rs/structopt/0.3.14/structopt/) for argument parsing. I didn't use `structopt` here as I wanted to keep things simple and mainly focus on the workflow, show how we can evolve functionality in small testable steps. I'm hoping the value of this workflow will be more apparent as we add more functionality to this cli, and things get more complex.

There are also a couple of things implemented here which you would not see in general Rust code. For example, returning a `String` as the `Err` value of a `Result`. This is ok, we will refactor them as we go. It's better to start with something small, get it working, then refactor than to try to make it perfect right away.

[^pem]: `PEM` encoding - see [Section 2](https://tools.ietf.org/html/rfc7468#section-2) of [RFC 7468](https://tools.ietf.org/html/rfc7468).
[^unitreturn]: This is not entirely accurate, if you do not specify a return type it will implicitly be **[()](https://doc.rust-lang.org/std/primitive.unit.html)**, the _unit_ type.
[^cargoq]: The `-q` in `cargo -q test` is short for `--quiet`.