Repository: thecoshman/http (language: rust)

Issue:
Starting two instances with auto port scan fails
### What occurs
```
$ httplz &
Hosting "." on port 8000 without TLS and no authentication...
Ctrl-C to stop.

$ httplz
Starting server failed.
```

Doing `http(lz) -p 8001` works, but I need to know all the ports I am using.

### What I expected 
The second command should start the server at port 8001.

EDIT: or at least more friendly errors telling me the reason of the failure; It is different from the error when doing `http -p 8000`

### Environments
macOS 10.14.6
http 1.5.1, build with `cargo install`



Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 2dc77d8..3a0285d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,7 @@ categories = ["network-programming", "web-programming::http-server"]
 license = "MIT"
 build = "build.rs"
 # Remember to also update in appveyor.yml
-version = "1.5.1"
+version = "1.5.2"
 # Remember to also update in http.md
 authors = ["thecoshman <rust@thecoshman.com>",
            "nabijaczleweli <nabijaczleweli@gmail.com>",
diff --git a/appveyor.yml b/appveyor.yml
index 8071d35..f941f70 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 1.5.1-{build}
+version: 1.5.2-{build}
 
 skip_tags: false
 
@@ -22,21 +22,21 @@ build: off
 build_script:
   - git submodule update --init --recursive
   - cargo build --verbose --release
-  - cp target\release\http.exe http-v1.5.1.exe
-  - strip --strip-all --remove-section=.comment --remove-section=.note http-v1.5.1.exe
-  - makensis -DHTTP_VERSION=v1.5.1 "/X!include \"EnvVarUpdate.nsh\"" install.nsi
+  - cp target\release\http.exe http-v1.5.2.exe
+  - strip --strip-all --remove-section=.comment --remove-section=.note http-v1.5.2.exe
+  - makensis -DHTTP_VERSION=v1.5.2 "/X!include \"EnvVarUpdate.nsh\"" install.nsi
 
 test: off
 test_script:
   - cargo test --verbose --release
 
 artifacts:
-  - path: http-v1.5.1.exe
-  - path: http v1.5.1 installer.exe
+  - path: http-v1.5.2.exe
+  - path: http v1.5.2 installer.exe
 
 deploy:
   provider: GitHub
-  artifact: /http.*v1.5.1.*\.exe/
+  artifact: /http.*v1.5.2.*\.exe/
   auth_token:
     secure: ZTXvCrv9y01s7Hd60w8W7NaouPnPoaw9YJt9WhWQ2Pep8HLvCikt9Exjkz8SGP9P
   on:
diff --git a/src/error.rs b/src/error.rs
index 4a5de3f..2e6e439 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,76 +1,42 @@
 use self::super::util::uppercase_first;
 use std::borrow::Cow;
-use std::io::Write;
+use std::fmt;
 
 
-/// Enum representing all possible ways the application can fail.
+/// An application failure.
+///
+/// # Examples
+///
+/// ```
+/// # use https::Error;
+/// assert_eq!(Error {
+///                desc: "network",
+///                op: "write",
+///                more: "full buffer".into(),
+///            }.to_string(),
+///            "Writing network failed: full buffer.");
+/// ```
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
-pub enum Error {
-    /// An I/O error occured.
+pub struct Error {
+    /// The file the I/O operation regards.
+    pub desc: &'static str,
+    /// The failed operation.
     ///
-    /// This includes higher-level I/O errors like FS ones.
-    Io {
-        /// The file the I/O operation regards.
-        desc: &'static str,
-        /// The failed operation.
-        ///
-        /// This should be lowercase and imperative ("create", "open").
-        op: &'static str,
-        /// Additional data.
-        more: Option<Cow<'static, str>>,
-    },
+    /// This should be lowercase and imperative ("create", "open").
+    pub op: &'static str,
+    /// Additional data.
+    pub more: Cow<'static, str>,
 }
 
-impl Error {
-    /// Write the error message to the specified output stream.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use https::Error;
-    /// # use std::iter::FromIterator;
-    /// let mut out = Vec::new();
-    /// Error::Io {
-    ///     desc: "network",
-    ///     op: "write",
-    ///     more: Some("full buffer"),
-    /// }.print_error(&mut out);
-    /// assert_eq!(String::from_iter(out.iter().map(|&i| i as char)),
-    ///            "Writing network failed: full buffer.\n".to_string());
-    /// ```
-    pub fn print_error<W: Write>(&self, err_out: &mut W) {
-        match *self {
-            Error::Io { desc, op, ref more } => {
-                // Strip the last 'e', if any, so we get correct inflection for continuous times
-                let op = uppercase_first(if op.ends_with('e') {
-                    &op[..op.len() - 1]
-                } else {
-                    op
-                });
-                write!(err_out, "{}ing {} failed", op, desc).unwrap();
-                if let &Some(ref more) = more {
-                    write!(err_out, ": {}", more).unwrap();
-                }
-                writeln!(err_out, ".").unwrap();
-            }
-        }
-    }
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        // Strip the last 'e', if any, so we get correct inflection for continuous times
+        let op = uppercase_first(if self.op.ends_with('e') {
+            &self.op[..self.op.len() - 1]
+        } else {
+            self.op
+        });
 
-    /// Get the executable exit value from an `Error` instance.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use https::Error;
-    /// assert_eq!(Error::Io {
-    ///     desc: "",
-    ///     op: "",
-    ///     more: None,
-    /// }.exit_value(), 1);
-    /// ```
-    pub fn exit_value(&self) -> i32 {
-        match *self {
-            Error::Io { .. } => 1,
-        }
+        write!(f, "{}ing {} failed: {}.", op, self.desc, self.more)
     }
 }
diff --git a/src/main.rs b/src/main.rs
index 7a5e74a..793bebe 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -34,7 +34,6 @@ pub use error::Error;
 pub use options::Options;
 
 use iron::Iron;
-use std::io::stderr;
 use std::process::exit;
 use std::sync::{Arc, Mutex, Condvar};
 use hyper_native_tls::NativeTlsServer;
@@ -47,8 +46,8 @@ fn main() {
 
 fn actual_main() -> i32 {
     if let Err(err) = result_main() {
-        err.print_error(&mut stderr());
-        err.exit_value()
+        eprintln!("{}", err);
+        1
     } else {
         0
     }
@@ -66,21 +65,21 @@ fn result_main() -> Result<(), Error> {
     let mut responder = try!(if let Some(p) = opts.port {
         if let Some(&((ref id, _), ref pw)) = opts.tls_data.as_ref() {
                 Iron::new(ops::HttpHandler::new(&opts)).https(("0.0.0.0", p),
-                                                              try!(NativeTlsServer::new(id, pw).map_err(|_| {
-                    Error::Io {
+                                                              try!(NativeTlsServer::new(id, pw).map_err(|err| {
+                    Error {
                         desc: "TLS certificate",
                         op: "open",
-                        more: None,
+                        more: err.to_string().into(),
                     }
                 })))
             } else {
                 Iron::new(ops::HttpHandler::new(&opts)).http(("0.0.0.0", p))
             }
             .map_err(|_| {
-                Error::Io {
+                Error {
                     desc: "server",
                     op: "start",
-                    more: Some("port taken".into()),
+                    more: "port taken".into(),
                 }
             })
     } else {
diff --git a/src/ops.rs b/src/ops.rs
index 34d726f..910fddc 100644
--- a/src/ops.rs
+++ b/src/ops.rs
@@ -1087,11 +1087,11 @@ pub fn try_ports<H: Handler + Clone>(hndlr: H, from: u16, up_to: u16, tls_data:
         let ir = Iron::new(hndlr.clone());
         match if let Some(&((_, ref id), ref pw)) = tls_data.as_ref() {
             ir.https(("0.0.0.0", port),
-                     try!(NativeTlsServer::new(id, pw).map_err(|_| {
-                Error::Io {
+                     try!(NativeTlsServer::new(id, pw).map_err(|err| {
+                Error {
                     desc: "TLS certificate",
                     op: "open",
-                    more: None,
+                    more: err.to_string().into(),
                 }
             })))
         } else {
@@ -1099,21 +1099,22 @@ pub fn try_ports<H: Handler + Clone>(hndlr: H, from: u16, up_to: u16, tls_data:
         } {
             Ok(server) => return Ok(server),
             Err(error) => {
-                if !error.to_string().contains("port") {
-                    return Err(Error::Io {
+                let error_s = error.to_string();
+                if !error_s.contains("port") && !error_s.contains("in use") {
+                    return Err(Error {
                         desc: "server",
                         op: "start",
-                        more: None,
+                        more: error_s.into(),
                     });
                 }
             }
         }
     }
 
-    Err(Error::Io {
+    Err(Error {
         desc: "server",
         op: "start",
-        more: Some("no free ports".into()),
+        more: "no free ports".into(),
     })
 }
 
@@ -1130,15 +1131,15 @@ pub fn try_ports<H: Handler + Clone>(hndlr: H, from: u16, up_to: u16, tls_data:
 /// assert_eq!(pass, "");
 /// ```
 pub fn generate_tls_data(temp_dir: &(String, PathBuf)) -> Result<((String, PathBuf), String), Error> {
-    fn err(which: bool, op: &'static str, more: Option<Cow<'static, str>>) -> Error {
-        Error::Io {
+    fn err<M: Into<Cow<'static, str>>>(which: bool, op: &'static str, more: M) -> Error {
+        Error {
             desc: if which {
                 "TLS key generation process"
             } else {
                 "TLS identity generation process"
             },
             op: op,
-            more: more,
+            more: more.into(),
         }
     }
     fn exit_err(which: bool, process: &mut Child, exitc: &ExitStatus) -> Error {
@@ -1151,18 +1152,18 @@ pub fn generate_tls_data(temp_dir: &(String, PathBuf)) -> Result<((String, PathB
             stderr = "<error getting process stderr".to_string();
         }
 
-        err(which,
-            "exit",
-            Some(format!("{};\nstdout: ```\n{}```;\nstderr: ```\n{}```", exitc, stdout, stderr).into()))
+        err(which, "exit", format!("{};\nstdout: ```\n{}```;\nstderr: ```\n{}```", exitc, stdout, stderr))
     }
 
     let tls_dir = temp_dir.1.join("tls");
-    if !tls_dir.exists() && fs::create_dir_all(&tls_dir).is_err() {
-        return Err(Error::Io {
-            desc: "temporary directory",
-            op: "create",
-            more: None,
-        });
+    if !tls_dir.exists() {
+        if let Err(err) = fs::create_dir_all(&tls_dir) {
+            return Err(Error {
+                desc: "temporary directory",
+                op: "create",
+                more: err.to_string().into(),
+            });
+        }
     }
 
     let mut child = try!(Command::new("openssl")
@@ -1172,7 +1173,7 @@ pub fn generate_tls_data(temp_dir: &(String, PathBuf)) -> Result<((String, PathB
         .stdout(Stdio::piped())
         .stderr(Stdio::piped())
         .spawn()
-        .map_err(|_| err(true, "spawn", None)));
+        .map_err(|error| err(true, "spawn", error.to_string())));
     try!(child.stdin
         .as_mut()
         .unwrap()
@@ -1182,8 +1183,8 @@ pub fn generate_tls_data(temp_dir: &(String, PathBuf)) -> Result<((String, PathB
                            env!("CARGO_PKG_VERSION"),
                            "\nnabijaczleweli@gmail.com\n")
             .as_bytes())
-        .map_err(|_| err(true, "pipe", None)));
-    let es = try!(child.wait().map_err(|_| err(true, "wait", None)));
+        .map_err(|error| err(true, "pipe", error.to_string())));
+    let es = try!(child.wait().map_err(|error| err(true, "wait", error.to_string())));
     if !es.success() {
         return Err(exit_err(true, &mut child, &es));
     }
@@ -1195,8 +1196,8 @@ pub fn generate_tls_data(temp_dir: &(String, PathBuf)) -> Result<((String, PathB
         .stdout(Stdio::null())
         .stderr(Stdio::null())
         .spawn()
-        .map_err(|_| err(false, "spawn", None)));
-    let es = try!(child.wait().map_err(|_| err(false, "wait", None)));
+        .map_err(|error| err(false, "spawn", error.to_string())));
+    let es = try!(child.wait().map_err(|error| err(false, "wait", error.to_string())));
     if !es.success() {
         return Err(exit_err(false, &mut child, &es));
     }


Test patch: