Implements Fan schema

This commit implements the Redfish Fan schema and fetches basic
information about each fan. The code first validates the chassis ID and
then validates the fan ID by obtaining a list of fans through the
endpoints of the cooled_by association. Additionally, common properties
are added.

Tested: Validator passes

1. doGet method to get Fan

'''
curl -k https://${bmc}/redfish/v1/Chassis/chassis/ThermalSubsystem/Fans/fan2
{
  "@odata.id": "/redfish/v1/Chassis/chassis/ThermalSubsystem/Fans/fan2",
  "@odata.type": "#Fan.v1_3_0.Fan",
  "Id": "fan2",
  "Name": "Fan"
}

2. Input the wrong chassisId with the doGet method to get Fan
curl -k https://${bmc}/redfish/v1/Chassis2/chassis/ThermalSubsystem/Fans/fan3
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "#Message.v1_1_1.Message",
        "Message": "The requested resource of type  named 'fan3' was not found.",
        "MessageArgs": [
          "",
          "fan3"
        ],
        "MessageId": "Base.1.13.0.ResourceNotFound",
        "MessageSeverity": "Critical",
        "Resolution": "Provide a valid resource identifier and resubmit the request."
      }
    ],
    "code": "Base.1.13.0.ResourceNotFound",
    "message": "The requested resource of type  named 'fan3' was not found."
  }
}

3. Input the wrong fanId with the doGet method to get fan
curl -k https://${bmc}/redfish/v1/Chassis/chassis/ThermalSubsystem/Fans/fan78
{
  "@odata.id": "/redfish/v1/Chassis/chassis/ThermalSubsystem/Fans/fan78",
  "@odata.type": "#Fan.v1_3_0.Fan",
  "Id": "fan78",
  "Name": "Fan",
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "#Message.v1_1_1.Message",
        "Message": "The requested resource of type Fan named 'fan78' was not found.",
        "MessageArgs": [
          "Fan",
          "fan78"
        ],
        "MessageId": "Base.1.13.0.ResourceNotFound",
        "MessageSeverity": "Critical",
        "Resolution": "Provide a valid resource identifier and resubmit the request."
      }
    ],
    "code": "Base.1.13.0.ResourceNotFound",
    "message": "The requested resource of type Fan named 'fan78' was not found."
  }
}
'''

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: I44994a998fd9c497d794e2568cc0148120bfbc15
Signed-off-by: Lakshmi Yadlapati <lakshmiy@us.ibm.com>
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index dc8dc81..3d8dae9 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -96,6 +96,7 @@
         requestRoutesPowerSupply(app);
         requestRoutesPowerSupplyCollection(app);
         requestRoutesThermalSubsystem(app);
+        requestRoutesFan(app);
         requestRoutesFanCollection(app);
 #endif
         requestRoutesManagerCollection(app);
diff --git a/redfish-core/lib/fan.hpp b/redfish-core/lib/fan.hpp
index 3a9572f..47d65ee 100644
--- a/redfish-core/lib/fan.hpp
+++ b/redfish-core/lib/fan.hpp
@@ -156,4 +156,151 @@
             std::bind_front(handleFanCollectionGet, std::ref(app)));
 }
 
+inline bool checkFanId(const std::string& fanPath, const std::string& fanId)
+{
+    std::string fanName = sdbusplus::message::object_path(fanPath).filename();
+
+    return !(fanName.empty() || fanName != fanId);
+}
+
+static inline void handleFanPath(
+    const std::string& fanId,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const dbus::utility::MapperGetSubTreePathsResponse& fanPaths,
+    const std::function<void(const std::string& fanPath,
+                             const std::string& service)>& callback)
+{
+    for (const auto& fanPath : fanPaths)
+    {
+        if (!checkFanId(fanPath, fanId))
+        {
+            continue;
+        }
+        dbus::utility::getDbusObject(
+            fanPath, fanInterface,
+            [fanPath, asyncResp,
+             callback](const boost::system::error_code& ec,
+                       const dbus::utility::MapperGetObject& object) {
+            if (ec || object.empty())
+            {
+                BMCWEB_LOG_ERROR << "DBUS response error on getDbusObject "
+                                 << ec.value();
+                messages::internalError(asyncResp->res);
+                return;
+            }
+            callback(fanPath, object.begin()->first);
+            });
+
+        return;
+    }
+    BMCWEB_LOG_WARNING << "Fan not found " << fanId;
+    messages::resourceNotFound(asyncResp->res, "Fan", fanId);
+}
+
+inline void getValidFanPath(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& validChassisPath, const std::string& fanId,
+    const std::function<void(const std::string& fanPath,
+                             const std::string& service)>& callback)
+{
+    getFanPaths(
+        asyncResp, validChassisPath,
+        [fanId, asyncResp, callback](
+            const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) {
+        handleFanPath(fanId, asyncResp, fanPaths, callback);
+        });
+}
+
+inline void addFanCommonProperties(crow::Response& resp,
+                                   const std::string& chassisId,
+                                   const std::string& fanId)
+{
+    resp.addHeader(boost::beast::http::field::link,
+                   "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
+    resp.jsonValue["@odata.type"] = "#Fan.v1_3_0.Fan";
+    resp.jsonValue["Name"] = "Fan";
+    resp.jsonValue["Id"] = fanId;
+    resp.jsonValue["@odata.id"] = boost::urls::format(
+        "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId);
+}
+
+inline void
+    afterGetValidFanPath(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                         const std::string& chassisId, const std::string& fanId,
+                         const std::string& fanPath, const std::string& service)
+{
+    BMCWEB_LOG_DEBUG << "fanPath = " << fanPath << " service = " << service;
+    addFanCommonProperties(asyncResp->res, chassisId, fanId);
+}
+
+inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                     const std::string& chassisId, const std::string& fanId,
+                     const std::optional<std::string>& validChassisPath)
+{
+    if (!validChassisPath)
+    {
+        messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
+        return;
+    }
+
+    getValidFanPath(
+        asyncResp, *validChassisPath, fanId,
+        std::bind_front(afterGetValidFanPath, asyncResp, chassisId, fanId));
+}
+
+inline void handleFanHead(App& app, const crow::Request& req,
+                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                          const std::string& chassisId,
+                          const std::string& fanId)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+    {
+        return;
+    }
+
+    redfish::chassis_utils::getValidChassisPath(
+        asyncResp, chassisId,
+        [asyncResp, chassisId,
+         fanId](const std::optional<std::string>& validChassisPath) {
+        if (!validChassisPath)
+        {
+            messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
+            return;
+        }
+        getValidFanPath(asyncResp, *validChassisPath, fanId,
+                        [asyncResp](const std::string&, const std::string&) {
+            asyncResp->res.addHeader(
+                boost::beast::http::field::link,
+                "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
+        });
+        });
+}
+
+inline void handleFanGet(App& app, const crow::Request& req,
+                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                         const std::string& chassisId, const std::string& fanId)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+    {
+        return;
+    }
+
+    redfish::chassis_utils::getValidChassisPath(
+        asyncResp, chassisId,
+        std::bind_front(doFanGet, asyncResp, chassisId, fanId));
+}
+
+inline void requestRoutesFan(App& app)
+{
+    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
+        .privileges(redfish::privileges::headFan)
+        .methods(boost::beast::http::verb::head)(
+            std::bind_front(handleFanHead, std::ref(app)));
+
+    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
+        .privileges(redfish::privileges::getFan)
+        .methods(boost::beast::http::verb::get)(
+            std::bind_front(handleFanGet, std::ref(app)));
+}
+
 } // namespace redfish