[ledger] Dynamicize Inspect API use

Test: Commit queue all the way.

Change-Id: Ibd2b70124f7b33eb88f11729ca7416dc914cdf0c
diff --git a/bin/ledger/app/app.cc b/bin/ledger/app/app.cc
index 038d1e2..864ae37 100644
--- a/bin/ledger/app/app.cc
+++ b/bin/ledger/app/app.cc
@@ -21,6 +21,7 @@
 #include <trace-provider/provider.h>
 #include <zircon/device/vfs.h>
 
+#include "peridot/bin/ledger/app/constants.h"
 #include "peridot/bin/ledger/app/ledger_repository_factory_impl.h"
 #include "peridot/bin/ledger/cobalt/cobalt.h"
 #include "peridot/bin/ledger/environment/environment.h"
@@ -110,8 +111,17 @@
     startup_context_->outgoing().object_dir()->set_prop(
         "statistic_gathering", app_params_.disable_statistics ? "off" : "on");
 
+    startup_context_->outgoing().object_dir()->set_children_callback(
+        {kRepositoriesInspectPathComponent},
+        [this](component::Object::ObjectVector* out) {
+          factory_impl_->GetChildren(out);
+        });
+
     loop_.Run();
 
+    startup_context_->outgoing().object_dir()->set_children_callback(
+        {kRepositoriesInspectPathComponent}, nullptr);
+
     return true;
   }
 
diff --git a/bin/ledger/app/constants.h b/bin/ledger/app/constants.h
index 7b3da32..cc84c4c 100644
--- a/bin/ledger/app/constants.h
+++ b/bin/ledger/app/constants.h
@@ -24,6 +24,10 @@
 // The serialization version of PageUsage DB.
 inline constexpr fxl::StringView kPageUsageDbSerializationVersion = "1";
 
+inline constexpr char kRepositoriesInspectPathComponent[] = "repositories";
+inline constexpr char kRequestsInspectPathComponent[] = "requests";
+inline constexpr char kLedgersInspectPathComponent[] = "ledgers";
+
 }  // namespace ledger
 
 #endif  // PERIDOT_BIN_LEDGER_APP_CONSTANTS_H_
diff --git a/bin/ledger/app/ledger_repository_factory_impl.cc b/bin/ledger/app/ledger_repository_factory_impl.cc
index e245a89..62554f1 100644
--- a/bin/ledger/app/ledger_repository_factory_impl.cc
+++ b/bin/ledger/app/ledger_repository_factory_impl.cc
@@ -9,6 +9,7 @@
 #include <unistd.h>
 
 #include <lib/backoff/exponential_backoff.h>
+#include <lib/component/cpp/expose.h>
 #include <lib/component/cpp/object_dir.h>
 #include <lib/fdio/util.h>
 #include <lib/fit/function.h>
@@ -24,6 +25,7 @@
 #include <zircon/processargs.h>
 #include <zircon/syscalls.h>
 
+#include "peridot/bin/ledger/app/constants.h"
 #include "peridot/bin/ledger/app/disk_cleanup_manager_impl.h"
 #include "peridot/bin/ledger/app/serialization_version.h"
 #include "peridot/bin/ledger/cloud_sync/impl/user_sync_impl.h"
@@ -76,8 +78,6 @@
 constexpr fxl::StringView kStagingPath = "staging";
 constexpr fxl::StringView kNamePath = "name";
 
-constexpr char kRepositoriesPath[] = "repositories";
-
 bool GetRepositoryName(rng::Random* random, const DetachedPath& content_path,
                        std::string* name) {
   DetachedPath name_path = content_path.SubPath(kNamePath);
@@ -125,6 +125,15 @@
     }
   };
 
+  // Forwards to the Inspect method of the wrapped LedgerRepositoryImpl, if the
+  // wrapped LedgerRepositoryImpl is present. Otherwise does nothing.
+  void Inspect(std::string display_name,
+               component::Object::ObjectVector* out) const {
+    if (ledger_repository_) {
+      ledger_repository_->Inspect(std::move(display_name), out);
+    }
+  }
+
   // Keeps track of |request| and |callback|. Binds |request| and fires
   // |callback| when the repository is available or an error occurs.
   void BindRepository(
@@ -236,6 +245,13 @@
 
 LedgerRepositoryFactoryImpl::~LedgerRepositoryFactoryImpl() {}
 
+void LedgerRepositoryFactoryImpl::GetChildren(
+    component::Object::ObjectVector* out) {
+  for (const auto& [name, container] : repositories_) {
+    container.Inspect(convert::ToHex(name), out);
+  }
+}
+
 void LedgerRepositoryFactoryImpl::GetRepository(
     zx::channel repository_handle,
     fidl::InterfaceHandle<cloud_provider::CloudProvider> cloud_provider,
@@ -306,15 +322,10 @@
   }
 
   DiskCleanupManagerImpl* disk_cleanup_manager_ptr = disk_cleanup_manager.get();
-  component::ExposedObject repository_exposed_object(
-      convert::ToHex(repository_information.name));
-  repository_exposed_object.set_parent(
-      inspect_object_dir_.find({kRepositoriesPath}));
   auto repository = std::make_unique<LedgerRepositoryImpl>(
-      std::move(repository_exposed_object), repository_information.ledgers_path,
-      environment_, std::move(db_factory), std::move(watchers),
-      std::move(user_sync), std::move(disk_cleanup_manager),
-      disk_cleanup_manager_ptr);
+      repository_information.ledgers_path, environment_, std::move(db_factory),
+      std::move(watchers), std::move(user_sync),
+      std::move(disk_cleanup_manager), disk_cleanup_manager_ptr);
   disk_cleanup_manager_ptr->SetPageEvictionDelegate(repository.get());
   container->SetRepository(Status::OK, std::move(repository));
 }
@@ -334,7 +345,7 @@
   user_config.user_directory = repository_information.content_path;
   user_config.cloud_provider = std::move(cloud_provider_ptr);
   fit::closure on_version_mismatch = [this, repository_information]() mutable {
-    OnVersionMismatch(std::move(repository_information));
+    OnVersionMismatch(repository_information);
   };
   auto cloud_sync = std::make_unique<cloud_sync::UserSyncImpl>(
       environment_, std::move(user_config), environment_->MakeBackoff(),
diff --git a/bin/ledger/app/ledger_repository_factory_impl.h b/bin/ledger/app/ledger_repository_factory_impl.h
index d43e888..86569ff 100644
--- a/bin/ledger/app/ledger_repository_factory_impl.h
+++ b/bin/ledger/app/ledger_repository_factory_impl.h
@@ -15,6 +15,7 @@
 #include <lib/callback/auto_cleanable.h>
 #include <lib/callback/cancellable.h>
 #include <lib/callback/managed_container.h>
+#include <lib/component/cpp/expose.h>
 #include <lib/fxl/files/unique_fd.h>
 #include <lib/fxl/macros.h>
 
@@ -40,6 +41,10 @@
       component::ObjectDir inspect_object_dir);
   ~LedgerRepositoryFactoryImpl() override;
 
+  // Populates |out| with children to be traversed (or not) during an
+  // inspection.
+  void GetChildren(component::Object::ObjectVector* out);
+
   // LedgerRepositoryFactoryErrorNotifierDelegate:
   void GetRepository(
       zx::channel repository_handle,
diff --git a/bin/ledger/app/ledger_repository_factory_impl_unittest.cc b/bin/ledger/app/ledger_repository_factory_impl_unittest.cc
index 4ef4e5f..87ce204 100644
--- a/bin/ledger/app/ledger_repository_factory_impl_unittest.cc
+++ b/bin/ledger/app/ledger_repository_factory_impl_unittest.cc
@@ -14,6 +14,7 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "peridot/bin/ledger/app/constants.h"
 #include "peridot/bin/ledger/testing/inspect.h"
 #include "peridot/bin/ledger/testing/test_with_environment.h"
 #include "peridot/lib/scoped_tmpfs/scoped_tmpfs.h"
@@ -28,18 +29,26 @@
 using ::testing::UnorderedElementsAre;
 
 constexpr fxl::StringView kObjectsName = "test objects";
-constexpr fxl::StringView kRepositoriesName = "repositories";
 constexpr fxl::StringView kUserID = "test user ID";
 
 class LedgerRepositoryFactoryImplTest : public TestWithEnvironment {
  public:
   LedgerRepositoryFactoryImplTest() {
-    object_dir_ = component::ObjectDir::Make({kObjectsName.data(), kObjectsName.size()});
+    object_dir_ =
+        component::ObjectDir::Make({kObjectsName.data(), kObjectsName.size()});
     repository_factory_ = std::make_unique<LedgerRepositoryFactoryImpl>(
         &environment_, nullptr, object_dir_);
+    object_dir_.set_children_callback(
+        {kRepositoriesInspectPathComponent},
+        [this](component::Object::ObjectVector* out) {
+          repository_factory_->GetChildren(out);
+        });
   }
 
-  ~LedgerRepositoryFactoryImplTest() override {}
+  ~LedgerRepositoryFactoryImplTest() override {
+    object_dir_.set_children_callback({kRepositoriesInspectPathComponent},
+                                      nullptr);
+  }
 
  protected:
   ::testing::AssertionResult CreateDirectory(std::string name);
@@ -145,7 +154,7 @@
   bool success = false;
 
   object_dir_.object()->OpenChild(
-      kRepositoriesName.ToString(), repositories_inspect_ptr->NewRequest(),
+      kRepositoriesInspectPathComponent, repositories_inspect_ptr->NewRequest(),
       callback::Capture(callback::SetWhenCalled(&callback_called), &success));
   RunLoopUntilIdle();
 
@@ -224,7 +233,7 @@
   EXPECT_EQ(kObjectsName, *object.name);
   EXPECT_THAT(*object.properties, IsEmpty());
   EXPECT_THAT(*object.metrics, IsEmpty());
-  EXPECT_THAT(*children, IsEmpty());
+  EXPECT_THAT(*children, ElementsAre(kRepositoriesInspectPathComponent));
 }
 
 TEST_F(LedgerRepositoryFactoryImplTest,
@@ -246,10 +255,9 @@
   ledger_internal::LedgerRepositoryPtr second_ledger_repository_ptr;
   ledger_internal::LedgerRepositoryPtr first_again_ledger_repository_ptr;
 
-  // Bindings to Inspect API "Inspect" objects. Over the course of the test the
-  // top-level object_dir_ will gain a "repositories" child which itself will
-  // gain two children (one for each created repository, with names chosen by
-  // the LedgerRepositoryFactoryImpl under test).
+  // Bindings to Inspect API "Inspect" objects. Because the Ledger objects'
+  // parent-child relationships are dynamically computed, these need to be
+  // unbound and rebound for each inspection of the repositories.
   fuchsia::inspect::InspectPtr repositories_inspect_ptr;
   fuchsia::inspect::InspectPtr first_repository_inspect_ptr;
   fuchsia::inspect::InspectPtr second_repository_inspect_ptr;
@@ -267,7 +275,7 @@
   // it is listed) and that it was requested once.
   ASSERT_TRUE(CallGetRepository(first_directory, &first_ledger_repository_ptr));
   ASSERT_TRUE(ListTopLevelChildren(&children_names));
-  EXPECT_THAT(*children_names, ElementsAre(kRepositoriesName.ToString()));
+  EXPECT_THAT(*children_names, ElementsAre(kRepositoriesInspectPathComponent));
   ASSERT_TRUE(OpenTopLevelRepositoriesChild(&repositories_inspect_ptr));
   ASSERT_TRUE(ListChildren(&repositories_inspect_ptr, &children_names));
   EXPECT_THAT(*children_names, SizeIs(1));
@@ -285,6 +293,9 @@
   // repositories were each requested once.
   ASSERT_TRUE(
       CallGetRepository(second_directory, &second_ledger_repository_ptr));
+  ASSERT_TRUE(ListTopLevelChildren(&children_names));
+  EXPECT_THAT(*children_names, ElementsAre(kRepositoriesInspectPathComponent));
+  ASSERT_TRUE(OpenTopLevelRepositoriesChild(&repositories_inspect_ptr));
   ASSERT_TRUE(ListChildren(&repositories_inspect_ptr, &children_names));
   EXPECT_THAT(*children_names, SizeIs(2));
   second_repository_name =
@@ -295,6 +306,8 @@
   EXPECT_THAT(*children_names, UnorderedElementsAre(first_repository_name,
                                                     second_repository_name));
   EXPECT_THAT(*second_repository_name, Not(IsEmpty()));
+  ASSERT_TRUE(OpenChild(&repositories_inspect_ptr, first_repository_name,
+                        &first_repository_inspect_ptr));
   ASSERT_TRUE(OpenChild(&repositories_inspect_ptr, second_repository_name,
                         &second_repository_inspect_ptr));
   ASSERT_TRUE(ReadData(&first_repository_inspect_ptr, &object));
@@ -310,9 +323,16 @@
   // respectively.
   ASSERT_TRUE(
       CallGetRepository(first_directory, &first_again_ledger_repository_ptr));
+  ASSERT_TRUE(ListTopLevelChildren(&children_names));
+  EXPECT_THAT(*children_names, ElementsAre(kRepositoriesInspectPathComponent));
+  ASSERT_TRUE(OpenTopLevelRepositoriesChild(&repositories_inspect_ptr));
   ASSERT_TRUE(ListChildren(&repositories_inspect_ptr, &children_names));
   EXPECT_THAT(*children_names, UnorderedElementsAre(first_repository_name,
                                                     second_repository_name));
+  ASSERT_TRUE(OpenChild(&repositories_inspect_ptr, first_repository_name,
+                        &first_repository_inspect_ptr));
+  ASSERT_TRUE(OpenChild(&repositories_inspect_ptr, second_repository_name,
+                        &second_repository_inspect_ptr));
   ASSERT_TRUE(ReadData(&first_repository_inspect_ptr, &object));
   EXPECT_EQ(first_repository_name, *object.name);
   ExpectRequestsMetric(&object, 2UL);
diff --git a/bin/ledger/app/ledger_repository_impl.cc b/bin/ledger/app/ledger_repository_impl.cc
index 1bab80a..a0ccbeb 100644
--- a/bin/ledger/app/ledger_repository_impl.cc
+++ b/bin/ledger/app/ledger_repository_impl.cc
@@ -4,8 +4,11 @@
 
 #include "peridot/bin/ledger/app/ledger_repository_impl.h"
 
+#include <lib/component/cpp/expose.h>
+#include <lib/component/cpp/object_dir.h>
 #include <trace/event.h>
 
+#include "peridot/bin/ledger/app/constants.h"
 #include "peridot/bin/ledger/app/page_utils.h"
 #include "peridot/bin/ledger/cloud_sync/impl/ledger_sync_impl.h"
 #include "peridot/bin/ledger/p2p_sync/public/ledger_communicator.h"
@@ -23,14 +26,13 @@
 }  // namespace
 
 LedgerRepositoryImpl::LedgerRepositoryImpl(
-    component::ExposedObject exposed_object, DetachedPath content_path,
-    Environment* environment, std::unique_ptr<storage::DbFactory> db_factory,
+    DetachedPath content_path, Environment* environment,
+    std::unique_ptr<storage::DbFactory> db_factory,
     std::unique_ptr<SyncWatcherSet> watchers,
     std::unique_ptr<sync_coordinator::UserSync> user_sync,
     std::unique_ptr<DiskCleanupManager> disk_cleanup_manager,
     PageUsageListener* page_usage_listener)
-    : exposed_object_(std::move(exposed_object)),
-      content_path_(std::move(content_path)),
+    : content_path_(std::move(content_path)),
       environment_(environment),
       db_factory_(std::move(db_factory)),
       encryption_service_factory_(environment),
@@ -43,15 +45,25 @@
   ledger_repository_debug_bindings_.set_empty_set_handler(
       [this] { CheckEmpty(); });
   disk_cleanup_manager_->set_on_empty([this] { CheckEmpty(); });
-  exposed_object_.object_dir().set_metric("requests", component::UIntMetric(0));
 }
 
 LedgerRepositoryImpl::~LedgerRepositoryImpl() {}
 
+void LedgerRepositoryImpl::Inspect(std::string display_name,
+                                   component::Object::ObjectVector* out) const {
+  auto object_dir = component::ObjectDir::Make(std::move(display_name));
+  object_dir.set_metric({kRequestsInspectPathComponent},
+                        component::UIntMetric(bindings_.size()));
+  object_dir.set_children_callback(
+      [/*this*/](component::Object::ObjectVector* out) {
+        // TODO(nathaniel): This is the next level down of inspection...
+      });
+  out->push_back(object_dir.object());
+}
+
 void LedgerRepositoryImpl::BindRepository(
     fidl::InterfaceRequest<ledger_internal::LedgerRepository>
         repository_request) {
-  exposed_object_.object_dir().add_metric("requests", 1);
   bindings_.emplace(this, std::move(repository_request));
 }
 
diff --git a/bin/ledger/app/ledger_repository_impl.h b/bin/ledger/app/ledger_repository_impl.h
index 7df63da..5e9ef79 100644
--- a/bin/ledger/app/ledger_repository_impl.h
+++ b/bin/ledger/app/ledger_repository_impl.h
@@ -7,8 +7,8 @@
 
 #include <fuchsia/ledger/internal/cpp/fidl.h>
 #include <fuchsia/modular/auth/cpp/fidl.h>
-#include <garnet/public/lib/component/cpp/exposed_object.h>
 #include <lib/callback/auto_cleanable.h>
+#include <lib/component/cpp/expose.h>
 #include <lib/fidl/cpp/interface_ptr_set.h>
 #include <lib/fit/function.h>
 #include <lib/fxl/files/unique_fd.h>
@@ -38,8 +38,7 @@
  public:
   // Creates a new LedgerRepositoryImpl object. Guarantees that |db_factory|
   // will outlive the given |disk_cleanup_manager|.
-  LedgerRepositoryImpl(component::ExposedObject exposed_object,
-                       DetachedPath content_path, Environment* environment,
+  LedgerRepositoryImpl(DetachedPath content_path, Environment* environment,
                        std::unique_ptr<storage::DbFactory> db_factory,
                        std::unique_ptr<SyncWatcherSet> watchers,
                        std::unique_ptr<sync_coordinator::UserSync> user_sync,
@@ -47,6 +46,11 @@
                        PageUsageListener* page_usage_listener);
   ~LedgerRepositoryImpl() override;
 
+  // Satisfies an inspection by adding to |out| an object with properties,
+  // metrics, and (callbacks affording access to) children of its own.
+  void Inspect(std::string display_name,
+               component::Object::ObjectVector* out) const;
+
   void set_on_empty(fit::closure on_empty_callback) {
     on_empty_callback_ = std::move(on_empty_callback);
   }
@@ -101,7 +105,6 @@
       fidl::InterfaceRequest<ledger_internal::LedgerDebug> request,
       GetLedgerDebugCallback callback) override;
 
-  component::ExposedObject exposed_object_;
   const DetachedPath content_path_;
   Environment* const environment_;
   std::unique_ptr<storage::DbFactory> db_factory_;
diff --git a/bin/ledger/app/ledger_repository_impl_unittest.cc b/bin/ledger/app/ledger_repository_impl_unittest.cc
index c3d7a31..378a9e6 100644
--- a/bin/ledger/app/ledger_repository_impl_unittest.cc
+++ b/bin/ledger/app/ledger_repository_impl_unittest.cc
@@ -7,7 +7,7 @@
 #include <fuchsia/inspect/cpp/fidl.h>
 #include <lib/callback/capture.h>
 #include <lib/callback/set_when_called.h>
-#include <lib/component/cpp/object_dir.h>
+#include <lib/component/cpp/expose.h>
 #include <lib/fit/function.h>
 #include <lib/fsl/vmo/strings.h>
 #include <lib/fxl/functional/make_copyable.h>
@@ -34,12 +34,9 @@
     auto fake_page_eviction_manager =
         std::make_unique<FakeDiskCleanupManager>();
     disk_cleanup_manager_ = fake_page_eviction_manager.get();
-    component::ExposedObject exposed_object = component::ExposedObject("test");
-    object_dir_ = exposed_object.object_dir();
 
     repository_ = std::make_unique<LedgerRepositoryImpl>(
-        std::move(exposed_object), DetachedPath(tmpfs_.root_fd()),
-        &environment_,
+        DetachedPath(tmpfs_.root_fd()), &environment_,
         std::make_unique<storage::fake::FakeDbFactory>(dispatcher()), nullptr,
         nullptr, std::move(fake_page_eviction_manager), disk_cleanup_manager_);
   }
@@ -49,7 +46,6 @@
  protected:
   scoped_tmpfs::ScopedTmpFS tmpfs_;
   FakeDiskCleanupManager* disk_cleanup_manager_;
-  component::ObjectDir object_dir_;
   std::unique_ptr<LedgerRepositoryImpl> repository_;
 
  private:
@@ -87,27 +83,48 @@
 }
 
 TEST_F(LedgerRepositoryImplTest, InspectAPIRequestsMetricOnMultipleBindings) {
+  // When nothing has bound to the repository, check that the "requests" metric
+  // is present and is zero.
   bool zeroth_callback_called = false;
   fuchsia::inspect::Object zeroth_read_object;
-  object_dir_.object()->ReadData(callback::Capture(
+  component::Object::ObjectVector zeroth_out;
+
+  repository_->Inspect("zeroth", &zeroth_out);
+
+  ASSERT_EQ(1UL, zeroth_out.size());
+  zeroth_out.at(0).get()->ReadData(callback::Capture(
       callback::SetWhenCalled(&zeroth_callback_called), &zeroth_read_object));
   EXPECT_TRUE(zeroth_callback_called);
   ExpectRequestsMetric(&zeroth_read_object, 0UL);
 
+  // When one binding has been made to the repository, check that the "requests"
+  // metric is present and is one.
   ledger_internal::LedgerRepositoryPtr first_ledger_repository_ptr;
-  repository_->BindRepository(first_ledger_repository_ptr.NewRequest());
   bool first_callback_called = false;
   fuchsia::inspect::Object first_read_object;
-  object_dir_.object()->ReadData(callback::Capture(
+  component::Object::ObjectVector first_out;
+  repository_->BindRepository(first_ledger_repository_ptr.NewRequest());
+
+  repository_->Inspect("first", &first_out);
+
+  ASSERT_EQ(1UL, first_out.size());
+  first_out.at(0).get()->ReadData(callback::Capture(
       callback::SetWhenCalled(&first_callback_called), &first_read_object));
   EXPECT_TRUE(first_callback_called);
   ExpectRequestsMetric(&first_read_object, 1UL);
 
+  // When two bindings have been made to the repository, check that the
+  // "requests" metric is present and is two.
   ledger_internal::LedgerRepositoryPtr second_ledger_repository_ptr;
-  repository_->BindRepository(second_ledger_repository_ptr.NewRequest());
   bool second_callback_called = false;
   fuchsia::inspect::Object second_read_object;
-  object_dir_.object()->ReadData(callback::Capture(
+  component::Object::ObjectVector second_out;
+  repository_->BindRepository(second_ledger_repository_ptr.NewRequest());
+
+  repository_->Inspect("second", &second_out);
+
+  ASSERT_EQ(1UL, second_out.size());
+  second_out.at(0).get()->ReadData(callback::Capture(
       callback::SetWhenCalled(&second_callback_called), &second_read_object));
   EXPECT_TRUE(second_callback_called);
   ExpectRequestsMetric(&second_read_object, 2UL);
diff --git a/bin/ledger/testing/inspect.cc b/bin/ledger/testing/inspect.cc
index dc9be8b..c85c22a 100644
--- a/bin/ledger/testing/inspect.cc
+++ b/bin/ledger/testing/inspect.cc
@@ -7,6 +7,7 @@
 #include <fuchsia/inspect/cpp/fidl.h>
 
 #include "gtest/gtest.h"
+#include "peridot/bin/ledger/app/constants.h"
 
 namespace ledger {
 
@@ -17,7 +18,7 @@
   unsigned long extra_requests_found = 0UL;
   unsigned long requests = 0UL;
   for (auto& index : *object->metrics) {
-    if (index.key == "requests") {
+    if (index.key == kRequestsInspectPathComponent) {
       if (!requests_found) {
         requests_found = true;
         requests = index.value.uint_value();