Add getAssetInfo into util function

AssetInfo is needed for many schemas like Fan, Storage etc. Using this
utility function, those AssetInfo can be obtained in one place.

This function extracts the following properties if available.
- Manufacturer
- Model
- PartNumber
- SerialNumber
- SparePartNumber (if requested)

Tested:
- GET Chassis, Fan, Storage etc
- Redfish Service Validator passes

Change-Id: I2e8fdab8969d24899c261cfdf601be971d0210d7
Signed-off-by: Myung Bae <myungbae@us.ibm.com>
diff --git a/redfish-core/include/utils/asset_utils.hpp b/redfish-core/include/utils/asset_utils.hpp
new file mode 100644
index 0000000..be97276
--- /dev/null
+++ b/redfish-core/include/utils/asset_utils.hpp
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+#pragma once
+
+#include "async_resp.hpp"
+#include "dbus_utility.hpp"
+#include "error_messages.hpp"
+#include "logging.hpp"
+#include "utils/dbus_utils.hpp"
+
+#include <asm-generic/errno.h>
+
+#include <boost/system/error_code.hpp>
+#include <nlohmann/json.hpp>
+#include <sdbusplus/unpack_properties.hpp>
+
+#include <functional>
+#include <memory>
+#include <ranges>
+#include <string>
+
+namespace redfish
+{
+namespace asset_utils
+{
+
+inline void extractAssetInfo(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const nlohmann::json::json_pointer& jsonKeyName,
+    const dbus::utility::DBusPropertiesMap& assetList,
+    bool includeSparePartNumber = false)
+{
+    const std::string* manufacturer = nullptr;
+    const std::string* model = nullptr;
+    const std::string* partNumber = nullptr;
+    const std::string* serialNumber = nullptr;
+    const std::string* sparePartNumber = nullptr;
+
+    const bool success = sdbusplus::unpackPropertiesNoThrow(
+        dbus_utils::UnpackErrorPrinter(), assetList, "Manufacturer",
+        manufacturer, "Model", model, "PartNumber", partNumber, "SerialNumber",
+        serialNumber, "SparePartNumber", sparePartNumber);
+    if (!success)
+    {
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    nlohmann::json& assetData = asyncResp->res.jsonValue[jsonKeyName];
+
+    if (manufacturer != nullptr)
+    {
+        assetData["Manufacturer"] = *manufacturer;
+    }
+    if (model != nullptr)
+    {
+        assetData["Model"] = *model;
+    }
+    if (partNumber != nullptr)
+    {
+        assetData["PartNumber"] = *partNumber;
+    }
+    if (serialNumber != nullptr)
+    {
+        assetData["SerialNumber"] = *serialNumber;
+    }
+    // SparePartNumber is optional on D-Bus so skip if it is empty
+    if (includeSparePartNumber && sparePartNumber != nullptr &&
+        !sparePartNumber->empty())
+    {
+        assetData["SparePartNumber"] = *sparePartNumber;
+    }
+}
+
+inline void getAssetInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                         const std::string& serviceName,
+                         const std::string& dbusPath,
+                         const nlohmann::json::json_pointer& jsonKeyName,
+                         bool includeSparePartNumber = false)
+{
+    dbus::utility::getAllProperties(
+        serviceName, dbusPath, "xyz.openbmc_project.Inventory.Decorator.Asset",
+        [asyncResp, jsonKeyName, includeSparePartNumber](
+            const boost::system::error_code& ec,
+            const dbus::utility::DBusPropertiesMap& assetList) {
+            if (ec)
+            {
+                if (ec.value() != EBADR)
+                {
+                    BMCWEB_LOG_ERROR("DBUS response error for Properties {}",
+                                     ec.value());
+                    messages::internalError(asyncResp->res);
+                }
+                return;
+            }
+            extractAssetInfo(asyncResp, jsonKeyName, assetList,
+                             includeSparePartNumber);
+        });
+}
+
+} // namespace asset_utils
+} // namespace redfish