PROTON-2092: Use default options if connect.json not found
- Previous behaviour was to throw an error.
diff --git a/cpp/include/proton/connect_config.hpp b/cpp/include/proton/connect_config.hpp
index d9d2c0f..76ac7a9 100644
--- a/cpp/include/proton/connect_config.hpp
+++ b/cpp/include/proton/connect_config.hpp
@@ -1,5 +1,5 @@
-#ifndef CONNECT_CONFIG_HPP
-#define CONNECT_CONFIG_HPP
+#ifndef PROTON_CONNECT_CONFIG_HPP
+#define PROTON_CONNECT_CONFIG_HPP
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -20,7 +20,10 @@
  * under the License.
  */
 
-#include <proton/connection_options.hpp>
+#include "./internal/export.hpp"
+
+#include <iosfwd>
+#include <string>
 
 namespace proton {
 
diff --git a/cpp/src/connect_config.cpp b/cpp/src/connect_config.cpp
index 6bb11c5..712796c 100644
--- a/cpp/src/connect_config.cpp
+++ b/cpp/src/connect_config.cpp
@@ -17,12 +17,13 @@
  * under the License.
  */
 
+#include <proton/connect_config.hpp>
+
 #include "msg.hpp"
 
-#include <proton/connect_config.hpp>
+#include <proton/connection_options.hpp>
 #include <proton/error.hpp>
 #include <proton/ssl.hpp>
-
 #include <proton/version.h>
 
 #include <json/value.h>
@@ -57,7 +58,6 @@
 }
 
 namespace proton {
-namespace connect_config {
 
 namespace {
 
@@ -90,8 +90,6 @@
 static const string HOME_FILE_NAME("/.config/messaging/" + FILE_NAME);
 static const string ETC_FILE_NAME("/etc/messaging/" + FILE_NAME);
 
-bool exists(const string& name) { return std::ifstream(name.c_str()).good(); }
-
 void parse_sasl(Value root, connection_options& opts) {
     Value sasl = get(objectValue, root, "sasl");
     opts.sasl_enabled(get_bool(sasl, "enable", true));
@@ -112,7 +110,7 @@
                   if (i > 0) s << " ";
                   s << v.asString();
               }
-              opts.sasl_allowed_mechs(s.str().c_str());
+              opts.sasl_allowed_mechs(s.str());
               break;
           }
           default:
@@ -149,43 +147,106 @@
 
 } // namespace
 
+std::string parse(Value root, connection_options& opts) {
+    std::ostringstream addr;
+
+    validate(objectValue, root, "configuration");
+
+    string scheme = get_string(root, "scheme", "amqps");
+    if (scheme != "amqp" && scheme != "amqps") {
+        throw err(msg() << "'scheme' must be \"amqp\" or \"amqps\"");
+    }
+
+    string host = get_string(root, "host", "localhost");
+    opts.virtual_host(host.c_str());
+    addr << host << ":";
+
+    Value port = root.get("port", scheme);
+    switch (port.type()) {
+        case stringValue:
+        addr << port.asString(); break;
+        case intValue:
+        case uintValue:
+        addr << port.asUInt(); break;
+        default:
+        throw err(msg() << "'port' expected string or uint, found " << port.type());
+    }
+
+    Value user = get(stringValue, root, "user");
+    if (!user.isNull()) opts.user(user.asString());
+    Value password = get(stringValue, root, "password");
+    if (!password.isNull()) opts.password(password.asString());
+
+    parse_sasl(root, opts);
+    parse_tls(scheme, root, opts);
+
+    return addr.str();
+}
+
+std::ifstream config_file(std::string& name) {
+    const char *env_path = getenv(ENV_VAR.c_str());
+    const char *home = getenv(HOME.c_str());
+
+    // Try environment variable if set
+    std::ifstream f;
+    if (env_path) {
+        name = env_path;
+        f.open(name);
+        return f;
+    }
+
+    std::vector<std::string> path;
+    // current directory
+    path.push_back(FILE_NAME);
+    // $HOME/.config/messaging/FILE_NAME
+    if (home) path.push_back(home + HOME_FILE_NAME);
+    // INSTALL_PREFIX/etc/messaging/FILE_NAME
+    if (PN_INSTALL_PREFIX && *PN_INSTALL_PREFIX) path.push_back(PN_INSTALL_PREFIX + ETC_FILE_NAME);
+
+    for (unsigned i = 0; i < path.size(); ++i) {
+        name = path[i];
+        f.open(name);
+        if (f.good()) return f;
+        f.close();
+    }
+
+    /* /etc/messaging/FILE_NAME */
+    name = ETC_FILE_NAME;
+    f.open(name);
+    return f;
+}
+
+string apply_config(connection_options& opts) {
+    std::string name;
+    std::ifstream f = config_file(name);
+    try {
+        Value root;
+        if (f.good()) {
+            f >> root;
+            f.close();
+        } else {
+            std::istringstream is("{}");
+            is >> root;
+        }
+        return parse(root, opts);
+    } catch (const std::ifstream::failure& e) {
+        throw err(msg() << "io error parsing '" << name << "': " << e.what());
+    } catch (const std::exception& e) {
+        throw err(msg() << "error parsing '" << name << "': " << e.what());
+    } catch (...) {
+        throw err(msg() << "error parsing '" << name << "'");
+    }
+}
+
+// Legacy Unsettled API
+
+namespace connect_config {
+
 std::string parse(std::istream& is, connection_options& opts) {
     try {
-        std::ostringstream addr;
-
         Value root;
         is >> root;
-        validate(objectValue, root, "configuration");
-
-        string scheme = get_string(root, "scheme", "amqps");
-        if (scheme != "amqp" && scheme != "amqps") {
-            throw err(msg() << "'scheme' must be \"amqp\" or \"amqps\"");
-        }
-
-        string host = get_string(root, "host", "localhost");
-        opts.virtual_host(host.c_str());
-        addr << host << ":";
-
-        Value port = root.get("port", scheme);
-        switch (port.type()) {
-          case stringValue:
-            addr << port.asString(); break;
-          case intValue:
-          case uintValue:
-            addr << port.asUInt(); break;
-          default:
-            throw err(msg() << "'port' expected string or uint, found " << port.type());
-        }
-
-        Value user = get(stringValue, root, "user");
-        if (!user.isNull()) opts.user(user.asString());
-        Value password = get(stringValue, root, "password");
-        if (!password.isNull()) opts.password(password.asString());
-
-        parse_sasl(root, opts);
-        parse_tls(scheme, root, opts);
-
-        return addr.str();
+        return parse(root, opts);
     } catch (const std::exception& e) {
         throw err(e.what());
     } catch (...) {
@@ -194,38 +255,27 @@
 }
 
 string default_file() {
-    /* Use environment variable if set */
-    const char *env_path = getenv(ENV_VAR.c_str());
-    if (env_path) return env_path;
-    /* current directory */
-    if (exists(FILE_NAME)) return FILE_NAME;
-    /* $HOME/.config/messaging/FILE_NAME */
-    const char *home = getenv(HOME.c_str());
-    if (home) {
-        string path = home + HOME_FILE_NAME;
-        if (exists(path)) return path;
+    std::string name;
+    std::ifstream f = config_file(name);
+    bool good = f.good();
+    f.close();
+    if (good || name!=ETC_FILE_NAME) {
+        return name;
     }
-    /* INSTALL_PREFIX/etc/messaging/FILE_NAME */
-    if (PN_INSTALL_PREFIX && *PN_INSTALL_PREFIX) {
-        string path = PN_INSTALL_PREFIX + ETC_FILE_NAME;
-        if (exists(path)) return path;
-    }
-    /* /etc/messaging/FILE_NAME */
-    if (exists(ETC_FILE_NAME)) return ETC_FILE_NAME;
-    throw err("no default configuration");
+    throw err("no default configuration, last tried: " + name);
 }
 
 string parse_default(connection_options& opts) {
-    string name = default_file();
-    std::ifstream f;
-    try {
-        f.exceptions(std::ifstream::badbit|std::ifstream::failbit);
-        f.open(name.c_str());
-    } catch (const std::exception& e) {
-        throw err(msg() << "error opening '" << name << "': " << e.what());
+    std::string name;
+    std::ifstream f = config_file(name);
+    if (!f.good()) {
+        throw err("no default configuration, last tried: " + name);
     }
     try {
-        return parse(f, opts);
+        Value root;
+        f >> root;
+        f.close();
+        return parse(root, opts);
     } catch (const std::ifstream::failure& e) {
         throw err(msg() << "io error parsing '" << name << "': " << e.what());
     } catch (const std::exception& e) {
diff --git a/cpp/src/connect_config.hpp b/cpp/src/connect_config.hpp
new file mode 100644
index 0000000..6219887
--- /dev/null
+++ b/cpp/src/connect_config.hpp
@@ -0,0 +1,32 @@
+#ifndef CONNECT_CONFIG_HPP
+#define CONNECT_CONFIG_HPP
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <string>
+
+namespace proton {
+
+class connection_options;
+
+std::string apply_config(connection_options& opts);
+
+}
+ #endif
diff --git a/cpp/src/connect_config_test.cpp b/cpp/src/connect_config_test.cpp
index 81dec87..541dcdc 100644
--- a/cpp/src/connect_config_test.cpp
+++ b/cpp/src/connect_config_test.cpp
@@ -40,6 +40,12 @@
 #include <cstdio>
 #include <cstdlib>
 
+// Used to inspect items from the proton-c level of the API
+#include "proton/internal/object.hpp"
+#include "proton/returned.hpp"
+#include <proton/transport.h>
+#include <proton/connection.h>
+
 // Windows has a different set of APIs for setting the environment
 #ifdef _WIN32
 #include <string.h>
@@ -205,7 +211,7 @@
     }
 };
 
-class test_default_connect : public test_handler {
+class test_almost_default_connect : public test_handler {
   public:
 
     void on_listener_start(container& c) PN_CPP_OVERRIDE {
@@ -220,6 +226,43 @@
     }
 };
 
+// Hack to use protected pn_object member of proton::object
+template <class P, class T>
+class internal_access_of: public P {
+
+public:
+    internal_access_of(const P& t) : P(t) {}
+    T* pn_object() { return proton::internal::object<T>::pn_object(); }
+};
+
+class test_default_connect : public test_handler {
+  public:
+
+    void on_listener_start(container& c) PN_CPP_OVERRIDE {
+        c.connect();
+    }
+
+    // on_transport_open will be called whatever happens to the connection whether it's
+    // connected or rejected due to error.
+    //
+    // We use a hacky way to get to the underlying C objects to check what's in them
+    void on_transport_open(proton::transport& transport) PN_CPP_OVERRIDE {
+        proton::connection connection = transport.connection();
+        internal_access_of<proton::connection, pn_connection_t> ic(connection);
+        pn_connection_t* c = ic.pn_object();
+        ASSERT_EQUAL(std::string("localhost"), pn_connection_get_hostname(c));
+    }
+
+    void on_transport_error(proton::transport& t) PN_CPP_OVERRIDE {
+        ASSERT_SUBSTRING("localhost:5671", t.error().description());
+        t.connection().container().stop();
+    }
+
+    void run() {
+        container(*this).run();
+    }
+};
+
 class test_host_user_pass : public test_handler {
   public:
 
@@ -358,6 +401,7 @@
     RUN_ARGV_TEST(failed, test_addr());
     RUN_ARGV_TEST(failed, test_invalid());
     RUN_ARGV_TEST(failed, test_default_connect().run());
+    RUN_ARGV_TEST(failed, test_almost_default_connect().run());
 
     bool have_sasl = pn_sasl_extended() && getenv("PN_SASL_CONFIG_PATH");
     pn_ssl_domain_t *have_ssl = pn_ssl_domain(PN_SSL_MODE_SERVER);
diff --git a/cpp/src/proactor_container_impl.cpp b/cpp/src/proactor_container_impl.cpp
index 8cab1c5..0f02891 100644
--- a/cpp/src/proactor_container_impl.cpp
+++ b/cpp/src/proactor_container_impl.cpp
@@ -20,7 +20,7 @@
 #include "proactor_container_impl.hpp"
 #include "proactor_work_queue_impl.hpp"
 
-#include "proton/connect_config.hpp"
+#include "connect_config.hpp"
 #include "proton/error_condition.hpp"
 #include "proton/listen_handler.hpp"
 #include "proton/listener.hpp"
@@ -383,7 +383,7 @@
 
 returned<connection> container::impl::connect() {
     connection_options opts;
-    std::string addr = connect_config::parse_default(opts);
+    std::string addr = apply_config(opts);
     return connect(addr, opts);
 }